/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/urm/org/apache/slide/urm/utils/resourceutilities/AbstractXMLBasedResourceBundle.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 org.apache.slide.urm.utils.resourceutilities.ResourceLocator.ResourceLocation;

import java.io.IOException;

import java.net.URL;

import java.util.Locale;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.WeakHashMap;
import java.util.ResourceBundle;
import java.util.Enumeration;
import java.util.Collections;
import java.util.MissingResourceException;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import org.xml.sax.helpers.DefaultHandler;


/**
 ** This class provides the common code base for various XML based ResourceBundles.
 ** <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/>
 **
 ** This class provides the same semantics as the original ResourceBundle class,
 ** means if you  call:<br/><br/>
 **
 ** <code>AbstractXMLBasedResourceBundle.getBundle("message", 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>
 ** message_de_DE.xml
 ** message_de.xml
 ** message_en_US.xml
 ** message_en.xml
 ** message.xml
 ** </pre>
 **
 ** @version $Revision: 1.4 $
 **
 ** @author eckehard.hermann@softwareag.com
 ** @author dieter.kessler@softwareag.com
 ** @author zsolt.sasvarie@softwareag.com
 **/
public abstract class AbstractXMLBasedResourceBundle extends ResourceBundle  {
    
    /**
     ** Enables/disables precondition testing due to a global setting.
     **/

    
    /**
     ** Enables/disables postcondition testing due to a global setting.
     **/
    
    /**
     ** The name of this class to use in log Statements.
     **/
   
    /**
     ** The Logger instance.
     **/
    
    
    
    /**
     ** A Map caching the loaded resources.
     **/
    protected static Map cache = null;
    
    
    /**
     ** Maps the resource keys to their values.
     **/
    protected Map resourceMap = null;
    
    /**
     ** The Locale associated with this ResourceBundle.
     **/
    protected Locale locale = null;
    
    /**
     ** The URL of the resource.
     **/
    protected URL resourceURL = null;
    
    
    /**
     ** Creates an empty AbstractXMLBasedResourceBundle.
     **/
    protected AbstractXMLBasedResourceBundle() {
    }
    
    /**
     ** Creates a AbstractXMLBasedResourceBundle.
     **
     ** @param resourceURL the URL of the resource.
     ** @param locale the Locale associated with this ResourceBundle.
     **
     ** @exception  ParserConfigurationException if parsing the XML document failed.
     ** @exception  SAXException if parsing the XML document failed.
     ** @exception  IOException if parsing the XML document failed.
     **/
    protected AbstractXMLBasedResourceBundle(URL resourceURL, Locale locale) throws SAXException, ParserConfigurationException, IOException {
        
        this.resourceURL = resourceURL;
        this.locale = locale;
        
        parseResource();
    }
    
    /**
     ** Returns the Locale associated with this ResourceBundle.
     **
     ** @return the Locale associated with this ResourceBundle.
     **/
    public Locale getLocale() {
        return locale;
    }
    
    /**
     ** Return the URL of the resource.
     **
     ** @return the URL of the resource.
     **/
    public URL getResourceURL() {
        return resourceURL;
    }
    
    
    /**
     ** Get an object from a ResourceBundle.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      key  the key of the value to return.
     **
     ** @return     the retrieved Object identified by the given <code>key</code>.
     **/
    public Object handleGetObject(String key) {
        
        Object object = null;
        if (resourceMap != null) {
            object = resourceMap.get(key);
        }
        AbstractXMLBasedResourceBundle parentBundle = (AbstractXMLBasedResourceBundle)parent;
        if ( (object == null) && (parentBundle != null) ) {
            object = parentBundle.handleGetObject(key);
        }
        
        return object;
    }
    
    
    /**
     ** Returns an enumeration of the keys.
     **
     ** @pre        true
     ** @post       true
     **
     ** @return     an enumeration of the keys.
     **/
    public Enumeration getKeys() {
        
        if (resourceMap == null) {
            resourceMap = new HashMap();
        }
        return Collections.enumeration(resourceMap.keySet());
    }
    
    
    /**
     ** 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) {
        
        if (resourceMap == null) {
            resourceMap = new HashMap();
        }
        if (value == null) {
            remove(key);
        }
        else {
            resourceMap.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) {
        
        String removedObject = null;
        if (resourceMap != null) {
            removedObject = (String)resourceMap.remove(key);
        }
        return removedObject;
    }
    
    
    /**
     ** Get the appropriate ResourceBundle subclass.
     **
     ** @pre       resourceBundleFactory != null
     ** @post      true
     **
     ** @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.
     ** @param     resourceBundleFactory  the XMLBasedResourceBundleFactory used to create a new
     **                                   ResourceBundle of the appropriate sublcass.
     **
     ** @return    the ResourceBundle     if one was found.
     **
     ** @exception MissingResourceException  may be thrown by a XMLResourceBundleFailureHandler
     **                                      to indicate that parsing the XML file failed.
     **/
    protected static ResourceBundle getBundle(String baseName, Locale locale, ClassLoader loader, XMLResourceBundleFailureHandler failureHandler, XMLBasedResourceBundleFactory resourceBundleFactory) throws MissingResourceException {
        
 
        AbstractXMLBasedResourceBundle bundle = null;
        
        Iterator resourceLocationIterator =
            ResourceLocator.getAllResourceLocations(baseName, "xml", locale, loader).iterator();
        
        if (resourceLocationIterator.hasNext()) {
            
            bundle = getBundle((ResourceLocation)resourceLocationIterator.next(), failureHandler, resourceBundleFactory);
            
            // now set up the 'chaining' to the parent ResourceBundles
            AbstractXMLBasedResourceBundle currentBundle = bundle;
            AbstractXMLBasedResourceBundle parentBundle = null;
            while ( (currentBundle != null) &&
                       (currentBundle.parent == null) &&
                       (resourceLocationIterator.hasNext()) ) {
                
                parentBundle = getBundle((ResourceLocation)resourceLocationIterator.next(), failureHandler, resourceBundleFactory);
                currentBundle.setParent(parentBundle);
                currentBundle = parentBundle;
            }
        }
        else {
            if (failureHandler != null) {
                failureHandler.handleResourceBundleNotFound(baseName, locale, loader);
            }
        }
        
        return bundle;
    }
    
    /**
     ** Returns the AbstractXMLBasedResourceBundle for the given ResourceLocation. If the
     ** AbstractXMLBasedResourceBundle is not already cached, the method tries to create
     ** a new one by parsing the corresponding XML file. If this fails (or if the
     ** given ResourceLocation is null), <code>null</code> is returned.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      resourceLocation       the ResourceLocation for which to return the
     **                                    appropriate AbstractXMLBasedResourceBundle.
     ** @param      failureHandler         the XMLResourceBundleFailureHandler used to react on failures.
     **                                    If <code>null</code>, any occurring failures will be ignored.
     ** @param      resourceBundleFactory  the XMLBasedResourceBundleFactory used to create a new
     **                                    ResourceBundle of the appropriate sublcass.
     **
     ** @return     the AbstractXMLBasedResourceBundle for the given ResourceLocation.
     **
     ** @exception  MissingResourceException  may be thrown by a XMLResourceBundleFailureHandler
     **                                       to indicate that parsing the XML file failed.
     **/
    private static AbstractXMLBasedResourceBundle getBundle(ResourceLocation resourceLocation, XMLResourceBundleFailureHandler failureHandler, XMLBasedResourceBundleFactory resourceBundleFactory) throws MissingResourceException {
        
        AbstractXMLBasedResourceBundle bundle = null;
        
        if (resourceLocation != null) {
            
            // have a look in the cache
            if (cache != null) {
                bundle = (AbstractXMLBasedResourceBundle)cache.get(resourceLocation);
            }
            
            if (bundle == null) {
                
                try {
                    
                    bundle = resourceBundleFactory.createXMLResourceBundle(resourceLocation.getURL(), resourceLocation.getLocale());
                    if (cache == null) {
                        cache = new WeakHashMap();
                    }
                    cache.put(resourceLocation, bundle);
                }
                catch (SAXException e) {
                     if (failureHandler != null) {
                        failureHandler.handleParseException(e, resourceLocation);
                    }
                }
                catch (ParserConfigurationException e) {
                    if (failureHandler != null) {
                        failureHandler.handleParseException(e, resourceLocation);
                    }
                }
                catch (IOException e) {
                    if (failureHandler != null) {
                        failureHandler.handleParseException(e, resourceLocation);
                    }
                }
            }
        }
        
        return bundle;
    }
    
    /**
     ** Parses the XML resource.
     **
     ** @exception  ParserConfigurationException if parsing the XML document failed.
     ** @exception  SAXException if parsing the XML document failed.
     ** @exception  IOException if parsing the XML document failed.
     **/
    protected void parseResource() throws SAXException, ParserConfigurationException, IOException {
        
        SAXParser parser = null;
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setNamespaceAware(false);
        factory.setValidating(false);
        
        parser = factory.newSAXParser();
        parser.parse(getResourceURL().toString(), getDefaultHandler());
    }
    
    /**
     ** 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.
     **/
    abstract protected DefaultHandler getDefaultHandler();
    
    /**
     ** Returns a String representation of the resource bundle.
     **
     ** @pre        true
     ** @post       true
     **
     ** @return     a String representation of the resource bundle.
     **/
    public String toString() {
        
        if (resourceMap == null) {
            resourceMap = new HashMap();
        }
        StringBuffer buffer = new StringBuffer(getClass().getName());
        buffer.append(resourceMap.toString());

        return buffer.toString();
    }
    
    /**
     ** The XMLBasedResourceBundleFactory used to provide AbstractXMLBasedResourceBundle
     ** a possibility to create instances of concrete subclasses and is passed to
     ** method {@link #getBundle getBundle}.
     **
     ** @version $Revision: 1.4 $
     **
     ** @author rsk@softwareag.com
     **/
    public static interface XMLBasedResourceBundleFactory {
        
        /**
         ** Creates a (subclass of) AbstractXMLBasedResourceBundle.
         **
         ** @pre        true
         ** @post       return != null
         **
         ** @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;
        
    }
    
}

