/*
 * Copyright (C) 2004 TiongHiang Lee
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not,  write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Email: thlee@thinklient.org
 */

package org.onemind.commons.java.xml.digest;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.*;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
 * The SaxDigesterHandler use digesters to digest the elements in the xml. The digesters can be added using the addDigester(). By
 * default the sequential digester list is used.
 * @author TiongHiang Lee (thlee@thinklient.org)
 * @version $Id: SaxDigesterHandler.java,v 1.3 2005/01/30 06:31:37 thlee Exp $ $Name:  $
 */
public class SaxDigesterHandler extends DefaultHandler
{

    /** the logger * */
    private static final Logger _logger = Logger.getLogger(SaxDigesterHandler.class.getName());

    /** the map contains subdigesters **/
    private Map _digesters = new HashMap();

    /** keep track of the path for current element **/
    private List _elementPath = new ArrayList();

    /**
     * Constructor
     */
    public SaxDigesterHandler()
    {
    }

    /**
     * Adding a digester to the root path
     * @param dig the digester
     */
    public void addDigester(ElementDigester dig)
    {
        addDigester(null, dig);
    }

    /**
     * Add a digester for the path
     * @param path the path
     * @param dig the digester
     */
    public void addDigester(String path, ElementDigester dig)
    {
        if (path == null)
        {
            _digesters.put(dig.getElementName(), dig);
        } else
        {
            _digesters.put(path + "/" + dig.getElementName(), dig);
        }
    }

    /**
     * Add a subdigester to current element path
     * @param dig the digester
     */
    public void addSubDigester(ElementDigester dig)
    {
        String path = getCurrentPath();
        addDigester(path, dig);
    }

    /**
     * Add a sub digester at a path of current path + prefixPath
     * @param prefixPath the prefix path
     * @param dig the digester
     */
    public void addSubDigester(String prefixPath, ElementDigester dig)
    {
        String path = getCurrentPath();
        if (path == null)
        {
            addDigester(prefixPath, dig);
        } else if (prefixPath == null)
        {
            addDigester(path, dig);
        } else
        {
            addDigester(path + "/" + prefixPath, dig);
        }
    }

    /**
     * Append the name to the element path and return the new path string
     * @param name the name of new element
     * @return the new path string
     */
    private String appendElementPath(String name)
    {
        int i = _elementPath.size();
        if (i > 0)
        {
            String str = (String) _elementPath.get(i - 1);
            String newStr = str + "/" + name;
            _elementPath.add(newStr);
            return newStr;
        } else
        {
            _elementPath.add(name);
            return name;
        }
    }

    /**
     * {@inheritDoc}
     */
    public void characters(char[] ch, int start, int length) throws SAXException
    {
        ElementDigester dig = getDigester(getCurrentPath());
        if (dig != null)
        {
            dig.characters(this, ch, start, length);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void endDocument() throws SAXException
    {
        //do nothing
    }

    /**
     * {@inheritDoc}
     */
    public void endElement(String uri, String localName, String qName) throws SAXException
    {
        String str = removeElementPath(qName);
        ElementDigester dig = (ElementDigester) getDigester(str);
        if (dig != null)
        {
            dig.endDigest(this);
        }
    }

    /**
     * Get the current element path
     * @return the path, or null if at start of document
     */
    public String getCurrentPath()
    {
        int i = _elementPath.size();
        if (i > 0)
        {
            return (String) _elementPath.get(i - 1);
        } else
        {
            return null;
        }
    }

    /**
     * Get the digester for particular path
     * @param path the path
     * @return the digester, or null if there's none found
     */
    private ElementDigester getDigester(String path)
    {
        return (ElementDigester) _digesters.get(path);
    }

    /**
     * Remove the element path
     * @param qname the element name is being removed
     * @return the path string before the path is removed
     */
    private String removeElementPath(String qname)
    {
        int i = _elementPath.size();
        if (i > 0)
        {
            String str = (String) _elementPath.remove(i - 1);
            return str;
        } else
        {
            throw new IllegalStateException("Cannot remove element path " + qname);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void startDocument() throws SAXException
    {
        //do nothing
    }

    /**
     * Call start of particular element digester, if there's any
     * @param attr the attribute
     * @param path the path
     * @throws SAXException
     */
    private void startDigest(String path, Attributes attr) throws SAXException
    {
        if (_logger.isLoggable(Level.FINEST))
        {
            _logger.finest("Digesting " + path);
        }
        ElementDigester dig = (ElementDigester) getDigester(path);
        if (dig != null)
        {
            if (_logger.isLoggable(Level.FINEST))
            {
                _logger.finest("with " + dig);
            }
            dig.startDigest(this, attr);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void startElement(String namespaceURI, String lName, // local name
            String qName, // qualified name
            Attributes attrs) throws SAXException
    {
        String newPath = appendElementPath(qName);
        startDigest(newPath, attrs);
    }

    /**
     * Parse an input
     * @param stream the stream
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws IOException
     */
    public synchronized void parse(InputStream stream) throws ParserConfigurationException, SAXException, IOException
    {
        SAXParserFactory fac = SAXParserFactory.newInstance();
        SAXParser parser = fac.newSAXParser();
        parser.parse(stream, this);
    }
}