/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/urm/org/apache/slide/urm/common/impl/URMConfiguratorXML.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.common.impl;

import java.util.*;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.parsers.*;
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.*;
import org.xml.sax.SAXException;

import org.apache.slide.urm.common.URMConfigurator;
import org.apache.slide.urm.utils.messagelogger.MessageLogger;
import org.apache.slide.urm.URMException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.OutputStream;

/**
 * A possible (recursive) implementation that uses a XML file
 * to load the data from. In this case the {@link #getProperties() }
 * returns attribute node name and value pairs of the current XML
 * node.
 * The getSubProperties() returns subnode name and
 * URMConfiurator object (containing the subnode as URMConfiurator
 * recusively) pairs. If there are more subnodes with the same name
 * than in place of one URMConfiurator object it will be a List object
 * containing URMConfiurator elements.
 *
 * @author eckehard.hermann@softwareag.com
 * @author dieter.kessler@softwareag.com
 * @author zsolt.sasvarie@softwareag.com
 */
public class URMConfiguratorXML implements URMConfigurator {

    private static org.apache.log4j.Logger msLogger =
       org.apache.log4j.Logger.getLogger(URMConfiguratorXML.class.getName());
       
    private static Object msDefConfSynchObj = new Object();
    
    private Properties mAttributeProps  = null;
    private Hashtable mSubnodeConfs     = null;
    private String mConfiguratorName    = null;
    private URMConfiguratorXML mRootConf   = null;
    private static URMConfiguratorXML mStaticConf = null;
    
    public static final String DEFAULT_CONFIG_FILE = "urm_config.xml";
    public static final String DEFAULT_CONFIG_FILE_CLASSNAME = "/urm_config.xml";
    public static final String DEFAULT_CONFIG_FILE_PATH = "URM_CONFIGFILE_PATH";
    public static final String DEFAULT_CONFIG_FILE_CLASSPATH = "URM_CONFIGFILE_CLASSPATH";

    /**
     * Returns the local element name of the passed XML document or fragment.
     *
     * @return the local element name of the passed XML document or fragment.
     */
    public String getName() {
        return mConfiguratorName;
    }

    /** Returns a configurator out of System property settings or the default
     * config file, if the paramerters are not defined.
     *
     * @throws ParserConfigurationException document builder exception.
     * @throws SAXException error occured during the XML parsing.
     * @throws IOException error in the input stream.
     * @throws URMException if an unpredictable internal error occurs.
     */
    public static URMConfiguratorXML newConfigfileConfigurator()
            throws ParserConfigurationException, SAXException,
                    IOException, URMException {
        synchronized (msDefConfSynchObj) {
            InputStream fis = null;
            String configFilePath = null;
            String configClassPath = null;
            
            // if the configurater has been loaded before, return this instance
            if (mStaticConf == null) {
                // load the config file name from System property
                configFilePath = System.getProperty(DEFAULT_CONFIG_FILE_PATH);
                try {
                    if (configFilePath == null || configFilePath.length() == 0) {
                        configClassPath = System.getProperty(DEFAULT_CONFIG_FILE_CLASSPATH);
                        if (configClassPath == null || configClassPath.length() == 0)
                            configClassPath = DEFAULT_CONFIG_FILE_CLASSNAME;
                        
                        // try to load file from classpath
                        fis = Class.forName("org.apache.slide.urm.common.impl.URMConfiguratorXML")
                                                .getResourceAsStream(configClassPath);
    
                        // if not found, load try to load default file from file system
                        if (fis == null) {
                            configFilePath = DEFAULT_CONFIG_FILE;
                            File file = new File(configFilePath);
                            configFilePath = file.getAbsolutePath();
                            fis = new FileInputStream(file);
                        }
    
                    // load file from file system
                    } else {
                        File file = new File(configFilePath);
                        configFilePath = file.getAbsolutePath();
                        fis = new FileInputStream(file);
                    }
        
                } catch (FileNotFoundException e) {
                    throw new URMException(msLogger, "E", e);
                } catch (ClassNotFoundException e) {
                    throw new URMException(msLogger, "E", e);
                }
             
                mStaticConf = new URMConfiguratorXML(fis);
                
                try {
                    fis.close();
                } catch (IOException e) {
                    throw new URMException(msLogger, "E", e);
                }
            }
            return mStaticConf;
        }
    }
    
    public synchronized static void setDefaultConfigurator(URMConfiguratorXML defConf) {
        synchronized (msDefConfSynchObj) {
            mStaticConf = defConf;
        }
    }
    
    private URMConfiguratorXML(URMConfiguratorXML rootConf, String name, Properties props) {
        mRootConf = rootConf;
        mConfiguratorName = name;
        mAttributeProps = props;
    }
    
    /** Creates a configurator from a input stream containing an XML
     * document.
     *
     * @param configXml input stream providing a valid XML document.
     *
     * @throws ParserConfigurationException document builder exception.
     * @throws SAXException error occured during the XML parsing.
     * @throws IOException error in the input stream.
     * @throws URMException if an unpredictable internal error occurs.
     */
    public URMConfiguratorXML(InputStream configXml)
            throws ParserConfigurationException, SAXException,
                    IOException, URMException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        
        DocumentBuilder dbilder = dbf.newDocumentBuilder();
        Document doc = dbilder.parse(configXml);
        mRootConf = this;
        buildPropertiesFromXml(doc.getDocumentElement());
        
    }

    /** Creates a configurator from a input stream containing an XML
     * document.
     *
     * @param config an element node of an XML document.
     *
     * @throws URMException if an unpredictable internal error occurs.
     */
    public URMConfiguratorXML(Element config) throws URMException {
        mRootConf = this;
        buildPropertiesFromXml(config);
    }
    
    private URMConfiguratorXML(Element config, URMConfiguratorXML root) throws URMException {
        mRootConf = root;
        buildPropertiesFromXml(config);
    }

    private void buildPropertiesFromXml(Element inElement)
            throws URMException {

        if (inElement == null)
            throw new URMException(MessageLogger.getAndLogMessage(msLogger, "URMCOC0013"));
        
        mAttributeProps = new Properties();
        mSubnodeConfs   = new Hashtable();
        mConfiguratorName = inElement.getLocalName();
        
        NamedNodeMap nnm = inElement.getAttributes();
        Node node = null;
        String val = null, nn = null;
        for (int i = 0; (node = nnm.item(i)) != null; ++i)
            if (((nn = node.getLocalName()) != null) && (val = node.getNodeValue()) != null)
                mAttributeProps.setProperty(nn, val);
        
        NodeList nl = inElement.getChildNodes();
        for (int i = 0; (node = nl.item(i)) != null; ++i)
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                nn = node.getLocalName();
                Object obj = mSubnodeConfs.remove(nn);
                URMConfiguratorXML subconf = new URMConfiguratorXML((Element)node, mRootConf);
                if (obj != null)
                    if (obj instanceof List)
                        ((List)obj).add(subconf);
                    else {
                        List ls = new Vector();
                        ls.add(obj);
                        ls.add(subconf);
                        obj = ls;
                    }
                else
                    obj = subconf;
                mSubnodeConfs.put(nn, obj);
            }
    }

    /* (non-Javadoc)
     * @see com.softwareag.urm.common.URMConfigurator#getProperties()
     */
    public Properties getProperties() {
        return mAttributeProps;
    }

    /* (non-Javadoc)
     * @see com.softwareag.urm.common.URMConfigurator#getSubProperties()
     */
    public Map getSubConfigurators() {
        return mSubnodeConfs;
    }

    /* (non-Javadoc)
     * @see com.softwareag.urm.common.URMConfigurator#getRootConfigurator()
     */
    public URMConfigurator getRootConfigurator() {
        return mRootConf;
    }

    /* (non-Javadoc)
     * @see com.softwareag.urm.common.URMConfigurator#getProperties(java.lang.String)
     */
    public URMConfigurator getSubConfigurator(String key) {
        //StringTokenizer st = new StringTokenizer(key, "�\t\n\r\f");
        int i = key.indexOf('/');
        if (i > 0) {
            Object obj = mSubnodeConfs.get(key.substring(0, i));
            return obj == null ? null : ((URMConfiguratorXML)obj).getSubConfigurator(key.substring(++i));
        }
        else
            if (i < 0)
                return (URMConfiguratorXML)mSubnodeConfs.get(key);
            else
                return mRootConf.getSubConfigurator(key.substring(1));
    }
    
    public String setPropery(String key, String value) {
        return (String)mAttributeProps.setProperty(key, value);
    }
    
    private void addSubConfigurator(URMConfigurator subConf) {
        if (subConf == null && subConf.getName() == null)
            return;
        String nn = subConf.getName();
        Object obj = mSubnodeConfs.get(nn);
        if (obj == null) {
            mSubnodeConfs.put(nn, subConf);
        }
        else if (obj instanceof URMConfigurator) {
            List ls = new Vector();
            ls.add(obj);
            ls.add(subConf);
            mSubnodeConfs.put(nn, ls);
        }
        else if (obj instanceof List) {
            ((List)obj).add(subConf);
        }
    }

    public void addSubConfigurator(String name, Properties props) {
        addSubConfigurator(new URMConfiguratorXML(mRootConf, name, props));
    }
    
    public URMConfigurator setSubConfigurator(URMConfigurator subConf) {
        if (subConf != null && subConf.getName() != null)
            return (URMConfigurator)mSubnodeConfs.put(subConf.getName(), subConf);
        return null;
    }

    public URMConfigurator removeSubConfigurator(String subConfName) {
        if (subConfName != null)
            return (URMConfigurator)mSubnodeConfs.remove(subConfName);
        return null;
    }
    
    public void getConfiguratorAsStream(OutputStream outstream) throws URMException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        Document xmldoc = null;
        try {
            DocumentBuilder dbilder = dbf.newDocumentBuilder();
            xmldoc = dbilder.newDocument();
        } catch (ParserConfigurationException e) {
            throw new URMException(msLogger, "E", e);
        }
        Element el = this.createSubElements(xmldoc, this);
        TransformerFactory transf = TransformerFactory.newInstance();
        try {
            Transformer trns = transf.newTransformer();
            trns.transform(new DOMSource(el), new StreamResult(outstream));
        } catch (TransformerConfigurationException e) {
            throw new URMException(msLogger, "E", e);
        } catch (TransformerException e) {
            throw new URMException(msLogger, "E", e);
        }
    }
    
    private Element createSubElements(Document doc, URMConfigurator inConf) {
        if (inConf.getName() == null)
            return null;
        Element el = doc.createElement(inConf.getName());
        Properties props = inConf.getProperties();
        for (Iterator iter = props.keySet().iterator(); iter.hasNext();) {
            String key = (String)iter.next();
            el.setAttribute(key, props.getProperty(key));
        }
        Map subconf = inConf.getSubConfigurators();
        for (Iterator iter = subconf.values().iterator(); iter.hasNext();) {
            Object obj = iter.next();
            //System.out.println(obj.getClass().getName());
            if (obj instanceof List) {
                for (Iterator liter = ((List)obj).iterator(); liter.hasNext();) {
                    Element subel = createSubElements(doc, (URMConfigurator)liter.next());
                    if (subel != null)
                        el.appendChild(subel);
                }
            }
            else {
                Element subel = createSubElements(doc, (URMConfigurator)obj);
                if (subel != null)
                    el.appendChild(subel);
            }
        }
        return el;
    }
    
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
        if (!(obj instanceof URMConfigurator))
            return false;
        URMConfigurator inconf = (URMConfigurator)obj;
        if (inconf.getName() == null || !inconf.getName().equals(this.getName()))
            return false;
        Properties thisprops = this.getProperties();
        Properties inprops = inconf.getProperties();
        if (inprops.size() != thisprops.size())
            return false;
        for (Iterator iter = inprops.keySet().iterator(); iter.hasNext();) {
            String key = (String)iter.next();
            String val1 = inprops.getProperty(key), val2 = thisprops.getProperty(key);
            if (val2 == null || !val1.equals(val2))
                return false;
        }
        Map insubconfs = inconf.getSubConfigurators();
        Map thissubconfs = this.getSubConfigurators();
        if (insubconfs == null && thissubconfs == null)
            return true;
        if ((insubconfs != null && thissubconfs == null) ||
                (insubconfs == null && thissubconfs != null) ||
                insubconfs.size() != thissubconfs.size())
            return false;
        for (Iterator it1 = thissubconfs.keySet().iterator(); it1.hasNext();) {
            String key = (String)it1.next();
            Object obj1 = thissubconfs.get(key), obj2 = insubconfs.get(key);
            if (obj1 == null || obj2 == null)
                return false;
            if (obj1 instanceof List) {
                if (obj2 instanceof List) {
                    if (!equalsURMConfiguratorList((List)obj1, (List)obj2))
                        return false;
                }
                else
                    return false;
            }
            else {
                if (obj1 instanceof URMConfigurator && obj2 instanceof URMConfigurator) {
                    if (!((URMConfigurator)obj1).equals(obj2))
                        return false;
                }
                else
                    return false;
            }
        }
        return true;
    }
    
    private boolean equalsURMConfiguratorList(List list1, List list2) {
        if (list1 == null || list2 == null || list1.size() != list2.size())
            return false;
        for (Iterator it1 = list1.iterator(), it2 = list2.iterator(); it1.hasNext();) {
            if (!((URMConfigurator)it1.next()).equals(it2.next()))
                return false;
        }
        return true;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return toString(0, mConfiguratorName, mAttributeProps, mSubnodeConfs);
    }
    
    private String toString(int leftSpaces, String name,
                Properties attrProps, Map subnodeProps) {
        if ((attrProps == null || attrProps.isEmpty()) &&
                (subnodeProps == null || subnodeProps.isEmpty()))
            return "";
             
        String retstr = "";
        for (int i = 0; i < leftSpaces; ++i)
            retstr += "  ";
        retstr += name + getAttrsAsString(attrProps) + "\r\n";
        
        Iterator keys = subnodeProps.keySet().iterator();
        while (keys.hasNext()) {
            String key = (String)keys.next();
            Object val = subnodeProps.get(key);
            if (val != null) {
                if (val instanceof List) {
                    for (int i = 0; i < ((List)val).size(); ++i) {
                        URMConfiguratorXML subconf = (URMConfiguratorXML)((List)val).get(i);
                        retstr += toString(leftSpaces + 1, key,
                            subconf.getProperties(), subconf.getSubConfigurators());
                    }
                }
                else
                    retstr += toString(leftSpaces + 1, key,
                        ((URMConfiguratorXML)val).getProperties(),
                        ((URMConfiguratorXML)val).getSubConfigurators());
            }
        }
        return retstr;
    }
    
    private String getAttrsAsString(Properties props) {
        Enumeration keys = props.keys();
        String retstr = " [ ";
        while (keys.hasMoreElements()) {
            String key = (String)keys.nextElement();
            Object val = props.get(key);
            if (val != null)
                retstr += key + "=\"" + val + "\" ";
        }
        return (retstr += "]");
    }

    /*
    public static void main(String args[]) throws Exception {
        if (args.length <= 0) {
            System.out.println("Command line argument config file is missing.");
            return;
        }
        
        URMConfiguratorXML conf = new URMConfiguratorXML(new java.io.FileInputStream(args[0]));
        System.out.println(conf);
        FileOutputStream outfile = new java.io.FileOutputStream("outprops.txt");
        outfile.write(conf.toString().getBytes());
    }
    */
}
