/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/urm/org/apache/slide/urm/utils/resourceutilities/XMLResourceBundle.java,v 1.4 2005/03/02 10:53:35 eckehard Exp $
 * $Revision: 1.4 $
 * $Date: 2005/03/02 10:53:35 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.slide.urm.utils.resourceutilities;

// import list

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import java.net.URL;

import java.util.Locale;
import java.util.ResourceBundle;
import java.util.MissingResourceException;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;
import org.xml.sax.Attributes;

import org.xml.sax.helpers.DefaultHandler;

/**
 ** This is an extension of java.util.ResourceBundle that uses an XML document file
 ** as resource.
 ** <br/>
 ** Due to implementation weaknesses of the java.util.ResourceBundle class you must
 ** use {@link #getBundle(String baseName, Locale locale, ClassLoader loader)
 ** getBundle(String baseName, Locale locale, ClassLoader loader)} and not one of the shorter
 ** alternatives with less parameters. But the parameters locale and loader may be <code>
 ** null</code>.<br/><br/>
 **
 ** Example: The XML document...<br/><br/>
 **
 ** <pre>
 ** &lt;?xml version="1.0" encoding="utf-8"?&gt;
 ** &lt;properties&gt;
 **     &lt;property key="title"&gt;Hello&lt;/property&gt;
 **     &lt;property key="message"&gt;Welcome to the party&lt;/property&gt;
 ** &lt;properties&gt;
 ** </pre>
 **
 ** ...will result in a ResourceBundle containing the following entries:<br/><br/>
 **
 ** <pre>
 ** title: Hello
 ** message: Welcome to the party
 ** </pre>
 **
 ** You can use this class like a regular ResourceBundle. You have the same internationalization
 ** support as in the original ResourceBundle class, means if you  call:<br/><br/>
 **
 ** <code>XMLResourceBundle.getBundle("resource", Locale.GERMANY, null)</code><br/><br/>
 **
 ** assuming the default locale is "en_US", the classpath will be searched for the
 ** following files:<br/><br/>
 **
 ** <pre>
 ** resource_de_DE.xml
 ** resource_de.xml
 ** resource_en_US.xml
 ** resource_en.xml
 ** resource.xml
 ** </pre>
 **
 ** There is also a {@link <a href="doc-files/XMLResourceBundle.dtd"> DTD</a>}
 ** that allows you to savely create/edit the XML documents.
 **
 **
 ** @version $Revision: 1.4 $
 **
 ** @author rsk@softwareag.com
 **/
public class XMLResourceBundle extends AbstractXMLBasedResourceBundle  {
    
    /**
     ** The XMLResourceBundleFactory used to provide AbstractXMLBasedResourceBundle
     ** a possibility to create instances of concrete subclasses.
     **
     ** @version $Revision: 1.4 $
     **
     ** @author rsk@softwareag.com
     **/
    protected static class XMLResourceBundleFactory implements XMLBasedResourceBundleFactory {
        
        /**
         ** Creates a (subclass of) AbstractXMLBasedResourceBundle.
         **
         ** @param      resourceURL  the URL of the resource.
         ** @param      locale       the Locale associated with this ResourceBundle.
         **
         ** @return     an instance of the concrete AbstractXMLBasedResourceBundle
         **             subclass.
         **
         ** @exception  ParserConfigurationException if parsing the XML document failed.
         ** @exception  SAXException if parsing the XML document failed.
         ** @exception  IOException if parsing the XML document failed.
         **/
        public AbstractXMLBasedResourceBundle createXMLResourceBundle(URL resourceURL, Locale locale) throws SAXException, ParserConfigurationException, IOException {
            return new XMLResourceBundle(resourceURL, locale);
        }
    }
    
    /**
     ** The name of the <code>&lt;properties&gt;</code> tag.
     **/
    public static final String PROPERTIES_TAG = "properties";
    
    /**
     ** The name of the <code>&lt;property&gt;</code> tag.
     **/
    public static final String PROPERTY_TAG = "property";
    
    /**
     ** The name of the <code>key</code> attribute.
     **/
    public static final String KEY_ATTRIBUTE = "key";
    
    /**
     ** The message of the SAXException that is thrown if the <code>key</code>
     ** attribute of a &lt;property&gt; element is missing.
     **/
    public static final String KEY_ATTRIBUTE_MISSING = "Attribute 'key' of element <property> must be set";
    
    /**
     ** The XMLResourceBundleFactory used to provide AbstractXMLBasedResourceBundle
     ** a possibility to create instances of concrete subclasses.
     **/
    protected static XMLResourceBundleFactory factory = new XMLResourceBundleFactory();
    
    
    /**
     ** Creates a an empty XMLResourceBundle.
     **/
    public XMLResourceBundle() {
    }
    
    /**
     ** Creates a XMLResourceBundle from the given <code>URL</code>.
     **
     ** @param      resourceURL the URL of the resource.
     **
     ** @throws     ParserConfigurationException if parsing the XML document failed.
     ** @throws     SAXException if parsing the XML document failed.
     ** @throws     IOException if parsing the XML document failed.
     **/
    public XMLResourceBundle(URL resourceURL) throws SAXException, ParserConfigurationException, IOException {
        this(resourceURL, null);
    }
    
    //    /**
    //     ** Creates a XMLResourceBundle from the given <code>source</code>.
    //     ** This constructor uses a transformation to create SAX events from the
    //     ** given <code>source</code>, so if you have an URL to parse from you
    //     ** should prefer {@link #XMLResourceBundle(URL) XMLResourceBundle(URL)}.
    //     **
    //     ** @param      source  the Source to parse from.
    //     **
    //     ** @throws     ParserConfigurationException if parsing the XML document failed.
    //     ** @throws     SAXException if parsing the XML document failed.
    //     ** @throws     IOException if parsing the XML document failed.
    //     ** @throws     TransformerException               if the transformation failed
    //     **                                                for any reason.
    //     ** @throws     TransformerConfigurationException  if creating a Transformer
    //     **                                                failed for any reason.
    //     **/
    //    public XMLResourceBundle(Source source) throws SAXException, ParserConfigurationException, IOException, TransformerConfigurationException, TransformerException {
//
    //        TransformerFactory transformerFactory = TransformerFactory.newInstance();
    //        Transformer transformer = transformerFactory.newTransformer();
    //        transformer.transform(source, new SAXResult(getDefaultHandler()));
    //    }
    
    
    /**
     ** Creates a XMLResourceBundle.
     **
     ** @param resourceURL the URL of the resource.
     ** @param locale the Locale associated with this ResourceBundle.
     **
     ** @throws     ParserConfigurationException if parsing the XML document failed.
     ** @throws     SAXException if parsing the XML document failed.
     ** @throws     IOException if parsing the XML document failed.
     **/
    protected XMLResourceBundle(URL resourceURL, Locale locale) throws SAXException, ParserConfigurationException, IOException {
        super(resourceURL, locale);
    }
    
    /**
     ** Retrieves the appropriate ResourceBundle.
     **
     ** @param baseName the base name of the resource (without extension).
     ** @param locale the desrired Locale. May be <code>null</code>.
     ** @param loader the ClassLoader to load the resource from. May be <code>null</code>.
     **
     ** @return the ResourceBundle if one was found.
     **/
    public static ResourceBundle getBundle(String baseName, Locale locale, ClassLoader loader) {
        return getBundle(baseName, locale, loader, null);
    }
    
    /**
     ** Retrieves the appropriate ResourceBundle.
     ** An <code>XMLResourceBundleFailureHandler</code> may be passed in order
     ** to react on parsing failures.
     **
     ** @param     baseName          the base name of the resource (without extension).
     ** @param     locale            the desrired Locale. May be <code>null</code>.
     ** @param     loader            the ClassLoader to load the resource from. May be <code>null</code>.
     ** @param     failureHandler    the XMLResourceBundleFailureHandler used to react on failures.
     **                              If <code>null</code>, any occurring failures will be ignored.
     **
     ** @return    the ResourceBundle if one was found.
     **
     ** @throws    MissingResourceException  may be thrown by a XMLResourceBundleFailureHandler to indicate
     **                                      that parsing the XML file failed.
     **/
    public static ResourceBundle getBundle(String baseName, Locale locale, ClassLoader loader, XMLResourceBundleFailureHandler failureHandler) throws MissingResourceException {
        return getBundle(baseName, locale, loader, failureHandler, factory);
    }
    
    
    /**
     ** Returns the DefaultHandler used to parse the XML document. It is the
     ** DefaultHandler's responsibility to fill the ResourceBundle with properties
     ** based on the content of XML document.
     **
     ** @pre        true
     ** @post       true
     **
     ** @return     the DefaultHandler used to parse the XML document.
     **/
    protected DefaultHandler getDefaultHandler() {
        return new SAXHandler(this);
    }
    
    /**
     ** Puts a key/value pair into this resource bundle.
     **
     ** @param      key    the key of the resource.
     ** @param      value  the value of the resource.
     **/
    public void put(String key, String value) {
        super.put(key, value);
    }
    
    /**
     ** Removes the value with the given (key) from this resource bundle.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      key  the key of the value to remove.
     **
     ** @return     the value that has been removed.
     **/
    public String remove(String key) {
        return super.remove(key);
    }
    
    //    /**
    //     ** Writes the XML representation of this bundle's content to the given
    //     ** <code>result</code>.
    //     **
    //     ** @pre        true
    //     ** @post       true
    //     **
    //     ** @param      result    the Result to write to.
    //     **
    //     ** @throws     ParserConfigurationException       if creating a DocumentBuilder
    //     **                                                failed for any reason.
    //     ** @throws     TransformerException               if the transformation failed
    //     **                                                for any reason.
    //     ** @throws     TransformerConfigurationException  if creating a Transformer
    //     **                                                failed for any reason.
    //     **/
    //    public void writeTo(Result result) throws ParserConfigurationException, TransformerConfigurationException, TransformerException {
    //        writeTo(result, false);
    //    }
//
    //    /**
    //     ** Writes the XML representation of this bundle's content to the given
    //     ** <code>result</code>.
    //     **
    //     ** @pre        true
    //     ** @post       true
    //     **
    //     ** @param      result    the Result to write to.
    //     ** @param      indent    indicates if addintional whitespaces should be added
    //     **                       when outputting the result.
    //     **
    //     ** @throws     ParserConfigurationException       if creating a DocumentBuilder
    //     **                                                failed for any reason.
    //     ** @throws     TransformerException               if the transformation failed
    //     **                                                for any reason.
    //     ** @throws     TransformerConfigurationException  if creating a Transformer
    //     **                                                failed for any reason.
    //     **/
    //    public void writeTo(Result result, boolean indent) throws ParserConfigurationException, TransformerConfigurationException, TransformerException {
    //        writeTo(result, null, indent);
    //    }
//
    //    /**
    //     ** Writes the XML representation of this bundle's content to the given
    //     ** <code>result</code> using the given encoding.
    //     **
    //     ** @pre        true
    //     ** @post       true
    //     **
    //     ** @param      result    the Result to write to.
    //     ** @param      encoding  the encoding to use.
    //     **
    //     ** @throws     ParserConfigurationException       if creating a DocumentBuilder
    //     **                                                failed for any reason.
    //     ** @throws     TransformerException               if the transformation failed
    //     **                                                for any reason.
    //     ** @throws     TransformerConfigurationException  if creating a Transformer
    //     **                                                failed for any reason.
    //     **/
    //    public void writeTo(Result result, String encoding) throws ParserConfigurationException, TransformerConfigurationException, TransformerException {
    //        writeTo(result, encoding, false);
    //    }
//
    //    /**
    //     ** Writes the XML representation of this bundle's content to the given
    //     ** <code>result</code> using the given encoding.
    //     **
    //     ** @pre        true
    //     ** @post       true
    //     **
    //     ** @param      result    the Result to write to.
    //     ** @param      encoding  the encoding to use.
    //     ** @param      indent    indicates if addintional whitespaces should be added
    //     **                       when outputting the result.
    //     **
    //     ** @throws     ParserConfigurationException       if creating a DocumentBuilder
    //     **                                                failed for any reason.
    //     ** @throws     TransformerException               if the transformation failed
    //     **                                                for any reason.
    //     ** @throws     TransformerConfigurationException  if creating a Transformer
    //     **                                                failed for any reason.
    //     **/
    //    public void writeTo(Result result, String encoding, boolean indent) throws ParserConfigurationException, TransformerConfigurationException, TransformerException {
//
    //        Node parentNode = null;
    //        if (result instanceof DOMResult) {
    //            parentNode = ((DOMResult)result).getNode();
    //        }
//
    //        if (parentNode == null) {
    //            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    //            DocumentBuilder builder = factory.newDocumentBuilder();
    //            parentNode = builder.newDocument();
    //        }
//
    //        Document document = null;
    //        if (parentNode instanceof Document) {
    //            document = (Document)parentNode;
    //        }
    //        else {
    //            document = parentNode.getOwnerDocument();
    //        }
//
    //        Element propertiesElement = document.createElement(PROPERTIES_TAG);
    //        parentNode.appendChild(propertiesElement);
//
    //        Enumeration keysEnum = getKeys();
    //        Element propertyElement = null;
    //        String key = null;
    //        Object value = null;
    //        while (keysEnum.hasMoreElements()) {
//
    //            key = (String)keysEnum.nextElement();
    //            value = getObject(key);
    //            propertyElement = document.createElement(PROPERTY_TAG);
    //            propertyElement.setAttribute(KEY_ATTRIBUTE, key);
    //            propertyElement.appendChild(document.createTextNode(value.toString()));
    //            propertiesElement.appendChild(propertyElement);
    //        }
//
    //        TransformerFactory transformerFactory = TransformerFactory.newInstance();
    //        Transformer transformer = transformerFactory.newTransformer();
    //        if (encoding != null) {
    //            transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
    //        }
    //        if (indent) {
    //            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    //        }
    //        transformer.transform(new DOMSource(propertiesElement), result);
    //    }
    
    /**
     ** This class handles the SAX events and generates appropriate entries
     ** in the given ResourceBundle.
     **
     ** @version $Revision: 1.4 $
     **
     ** @author rsk@softwareag.com
     **/
    protected static class SAXHandler extends DefaultHandler {
        
        /**
         ** String constant for tag name "property".
         **/
        public final static String PROPERTY = "property";
        
        /**
         ** String constant for attribute name "key".
         **/
        public final static String KEY = "key";
        
        /**
         ** String constant for "".
         **/
        public final static String EMPTY_STRING = "";
        
        
        
        
        /**
         ** The XMLResourceBundle this Handler "is working for".
         **/
        protected XMLResourceBundle resourceBundle = null;
        
        /**
         ** The key of the current property.
         **/
        protected String propertyKey = EMPTY_STRING;
        
        /**
         ** The value of the current property.
         **/
        protected StringBuffer propertyValue = new StringBuffer();
        
        
        
        /**
         ** Creates a SAXHandler.
         **
         ** @param resourceBundle the XMLResourceBundle this Handler "is working for".
         **/
        public SAXHandler(XMLResourceBundle resourceBundle) {
            
            this.resourceBundle = resourceBundle;
        }
        
        
        /**
         ** Called when the start tag of an element has been detected.
         **
         ** @param  namespaceURI   the namespace URI of the element (if existing).
         ** @param  localName      the local name, e.g. the name without namespace prefix.
         **                        If no namespace prefix is set, <code>null</code>
         **                        is returned.
         ** @param  qualifiedName  the (full qualified) name of the element.
         ** @param  attributes     the attributes of the element.
         **
         ** @throws SAXException   if parsing fails for any reason.
         **/
        public void startElement(String namespaceURI, String localName, String qualifiedName, Attributes attributes) throws SAXException {
            
            if (PROPERTY.equals(qualifiedName)) {
                propertyKey = attributes.getValue(KEY);
                if ( (propertyKey == null) || (propertyKey.length() == 0) ) {
                    throw new SAXException(KEY_ATTRIBUTE_MISSING);
                }
                propertyValue.setLength(0);
            }
        }
        
        /**
         ** Called when the end tag of an element has been detected.
         **
         ** @param  namespaceURI   the namespace URI of the element (if existing).
         ** @param  localName      the local name, e.g. the name without namespace prefix.
         **                        If no namespace prefix is set, <code>null</code>
         **                        is returned.
         ** @param  qualifiedName  the (full qualified) name of the element.
         **
         ** @throws SAXException   if parsing fails for any reason.
         **/
        public void endElement(String namespaceURI, String localName, String qualifiedName) throws SAXException {
            
            if (PROPERTY.equals(qualifiedName)) {
                if (propertyKey != EMPTY_STRING) {
                    resourceBundle.put(propertyKey, propertyValue.toString());
                }
            }
        }
        
        /**
         ** Called when characters (text) has been detected.
         **
         ** @param  ch      the char array containing the text.
         ** @param  start   the start offset of the char array.
         ** @param  length  the length of the char array.
         **
         ** @throws SAXException   if parsing fails for any reason.
         **/
        public void characters(char[] ch, int start, int length) throws SAXException {
            
            if (propertyKey != EMPTY_STRING) {
                propertyValue.append(ch, start, length);
            }
        }
        
        
        /**
         ** Called when a SAXException of severity <code>warning</code> occurred.
         **
         ** @param  e  the SAXException that occurred.
         **
         ** @throws  SAXException  that was passed as parameter.
         **/
        public void warning(SAXException e) throws SAXException {
            throw e;
        }
        
        /**
         ** Called when a SAXException of severity <code>error</code> occurred.
         **
         ** @param  e  the SAXException that occurred.
         **
         ** @throws  SAXException  that was passed as parameter.
         **/
        public void error(SAXException e) throws SAXException {
            throw e;
        }
        
        /**
         ** Called when a SAXException of severity <code>fatal error</code> occurred.
         **
         ** @param  e  the SAXException that occurred.
         **
         ** @throws  SAXException  that was passed as parameter.
         **/
        public void fatalError(SAXException e) throws SAXException {
            throw e;
        }
        
    }
    
    /**
     ** For testing purposes only.
     **
     ** @param     args  the parameters (not used here).
     **
     ** @throws    Exception  any Exception that may occur.
     **/
    public static void main(String[] args) throws Exception {
        
        XMLResourceBundle xmlResourceBundle = new XMLResourceBundle();
        
        xmlResourceBundle.put("path", "/usr/rsk/");
        xmlResourceBundle.put("width", "350");
        xmlResourceBundle.put("height", "200");
        
        File file = new File("properties.xml");
        
        FileOutputStream outputStream = new FileOutputStream(file);
        //        Result result = new StreamResult(outputStream);
        //        xmlResourceBundle.writeTo(result, true);
        //        outputStream.close();
//
        //        FileInputStream inputStream = new FileInputStream(file);
        //        Source source = new StreamSource(inputStream);
        //        xmlResourceBundle = new XMLResourceBundle(source);
        //        inputStream.close();
        //        System.out.println(xmlResourceBundle.toString());
//
        //        xmlResourceBundle = new XMLResourceBundle(file.toURL());
        //        System.out.println(xmlResourceBundle.toString());
//
//
        //        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //        DocumentBuilder builder = factory.newDocumentBuilder();
        //        Document document = builder.newDocument();
        //        Element parentNode = document.createElement("parentElement");
        //        result = new DOMResult(parentNode);
        //        xmlResourceBundle.writeTo(result);
//
        //        source = new DOMSource(parentNode);
        //        xmlResourceBundle = new XMLResourceBundle(source);
        System.out.println(xmlResourceBundle.toString());
    }
    
    
    /**
     ** </br></br></br></br>
     **
     ** <h3>Using XMLResourceBundle as a replacement for PropertyResourceBundle</h3>
     ** Now you can also {@link #XMLResourceBundle(javax.xml.transform.Source) read}
     ** /{@link #writeTo(javax.xml.transform.Result) write} an XMLResourceBundle
     ** from/to a given {@link javax.xml.transform.Source Source}/
     ** {@link javax.xml.transform.Result Result}.
     ** Also new are the methods {@link #put put()} and {@link #remove remove()}.</br>
     ** Due to these new features you can use XMLResourceBundle as a replacement for
     ** the {@link PropertyResourceBundle PropertyResourceBundle} for e.g.
     ** storing properties on the file system:
     **
     ** <pre>
     **     // create an empty XMLResourceBundle
     **     XMLResourceBundle xmlResourceBundle = new XMLResourceBundle();
     **
     **     // put some values in it
     **     xmlResourceBundle.put("path", "/usr/rsk/");
     **     xmlResourceBundle.put("width", "350");
     **     xmlResourceBundle.put("height", "200");
     **
     **     File file = new File("properties.xml");
     **
     **     // write it to a Result, in this case a file
     **     FileOutputStream outputStream = new FileOutputStream(file);
     **     Result result = new StreamResult(outputStream);
     **     xmlResourceBundle.writeTo(result, true);
     **     outputStream.close();
     **
     **     // Read it from a Source, in this case a file
     **     FileInputStream inputStream = new FileInputStream(file);
     **     Source source = new StreamSource(inputStream);
     **     xmlResourceBundle = new XMLResourceBundle(source);
     **     inputStream.close();
     **
     **     ....
     **     // or read directly from an URL is the faster alternative:
     **     xmlResourceBundle = new XMLResourceBundle(file.toURL());
     ** </pre>
     **
     ** <br/>
     ** You can also integrate the XML into an existing document. Assuming
     ** that the <code>&lt;properties&gt;</code> should be appended to an
     ** element named <code>&lt;settings&gt;</code> the code would look like this:
     ** <pre>
     **     // use the <code>&lt;settings&gt;</code> element as parent
     **     Element settings = ...
     **     ...
     **     // append the <code>&lt;properties&gt;</code> to the <code>&lt;settings&gt;</code> element
     **     result = new DOMResult(settings);
     **     xmlResourceBundle.writeTo(result);
     **     ...
     **     // now read the <code>&lt;properties&gt;</code> from beneath the
     **     // <code>&lt;settings&gt;</code> element
     **     source = new DOMSource(settings);
     **     xmlResourceBundle = new XMLResourceBundle(source);
     ** </pre>
     **
     **/
}



