/*
 * 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.codehaus.xfire.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.codehaus.xfire.XFireRuntimeException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Few simple utils to read DOM. This is originally from the Jakarta Commons
 * Modeler.
 * 
 * @author Costin Manolache
 */
public class DOMUtils
{
    /**
     * Get the trimed text content of a node or null if there is no text
     */
    public static String getContent(Node n)
    {
        if (n == null)
            return null;
        Node n1 = DOMUtils.getChild(n, Node.TEXT_NODE);

        if (n1 == null)
            return null;

        String s1 = n1.getNodeValue();
        return s1.trim();
    }

    /**
     * Get the first element child.
     * 
     * @param parent
     *            lookup direct childs
     * @param name
     *            name of the element. If null return the first element.
     */
    public static Node getChild(Node parent, String name)
    {
        if (parent == null)
            return null;
        Node first = parent.getFirstChild();
        if (first == null)
            return null;

        for (Node node = first; node != null; node = node.getNextSibling())
        {
            //System.out.println("getNode: " + name + " " +
            // node.getNodeName());
            if (node.getNodeType() != Node.ELEMENT_NODE)
                continue;
            if (name != null && name.equals(node.getNodeName()))
            {
                return node;
            }
            if (name == null)
            {
                return node;
            }
        }
        return null;
    }

    public static String getAttribute(Node element, String attName)
    {
        NamedNodeMap attrs = element.getAttributes();
        if (attrs == null)
            return null;
        Node attN = attrs.getNamedItem(attName);
        if (attN == null)
            return null;
        return attN.getNodeValue();
    }

    public static void setAttribute(Node node, String attName, String val)
    {
        NamedNodeMap attributes = node.getAttributes();
        Node attNode = node.getOwnerDocument().createAttribute(attName);
        attNode.setNodeValue(val);
        attributes.setNamedItem(attNode);
    }

    public static void removeAttribute(Node node, String attName)
    {
        NamedNodeMap attributes = node.getAttributes();
        attributes.removeNamedItem(attName);
    }

    /**
     * Set or replace the text value
     */
    public static void setText(Node node, String val)
    {
        Node chld = DOMUtils.getChild(node, Node.TEXT_NODE);
        if (chld == null)
        {
            Node textN = node.getOwnerDocument().createTextNode(val);
            node.appendChild(textN);
            return;
        }
        // change the value
        chld.setNodeValue(val);
    }

    /**
     * Find the first direct child with a given attribute.
     * 
     * @param parent
     * @param elemName
     *            name of the element, or null for any
     * @param attName
     *            attribute we're looking for
     * @param attVal
     *            attribute value or null if we just want any
     */
    public static Node findChildWithAtt(Node parent, String elemName,
            String attName, String attVal)
    {

        Node child = DOMUtils.getChild(parent, Node.ELEMENT_NODE);
        if (attVal == null)
        {
            while (child != null
                    && (elemName == null || elemName
                            .equals(child.getNodeName()))
                    && DOMUtils.getAttribute(child, attName) != null)
            {
                child = getNext(child, elemName, Node.ELEMENT_NODE);
            }
        }
        else
        {
            while (child != null
                    && (elemName == null || elemName
                            .equals(child.getNodeName()))
                    && !attVal.equals(DOMUtils.getAttribute(child, attName)))
            {
                child = getNext(child, elemName, Node.ELEMENT_NODE);
            }
        }
        return child;
    }

    /**
     * Get the first child's content ( ie it's included TEXT node ).
     */
    public static String getChildContent(Node parent, String name)
    {
        Node first = parent.getFirstChild();
        if (first == null)
            return null;
        for (Node node = first; node != null; node = node.getNextSibling())
        {
            //System.out.println("getNode: " + name + " " +
            // node.getNodeName());
            if (name.equals(node.getNodeName()))
            {
                return getContent(node);
            }
        }
        return null;
    }

    /**
     * Get the first direct child with a given type
     */
    public static Node getChild(Node parent, int type)
    {
        Node n = parent.getFirstChild();
        while (n != null && type != n.getNodeType())
        {
            n = n.getNextSibling();
        }
        if (n == null)
            return null;
        return n;
    }

    /**
     * Get the next sibling with the same name and type
     */
    public static Node getNext(Node current)
    {
        String name = current.getNodeName();
        int type = current.getNodeType();
        return getNext(current, name, type);
    }

    /**
     * Return the next sibling with a given name and type
     */
    public static Node getNext(Node current, String name, int type)
    {
        Node first = current.getNextSibling();
        if (first == null)
            return null;

        for (Node node = first; node != null; node = node.getNextSibling())
        {

            if (type >= 0 && node.getNodeType() != type)
                continue;
            //System.out.println("getNode: " + name + " " +
            // node.getNodeName());
            if (name == null)
                return node;
            if (name.equals(node.getNodeName()))
            {
                return node;
            }
        }
        return null;
    }

    public static class NullResolver implements EntityResolver
    {
        public InputSource resolveEntity(String publicId, String systemId)
                throws SAXException, IOException
        {
            return new InputSource(new StringReader(""));
        }
    }

    /**
     * Read XML as DOM.
     */
    public static Document readXml(InputStream is) throws SAXException,
            IOException, ParserConfigurationException
    {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

        dbf.setValidating(false);
        dbf.setIgnoringComments(false);
        dbf.setIgnoringElementContentWhitespace(true);
        dbf.setNamespaceAware(true);
        //dbf.setCoalescing(true);
        //dbf.setExpandEntityReferences(true);

        DocumentBuilder db = null;
        db = dbf.newDocumentBuilder();
        db.setEntityResolver(new NullResolver());

        // db.setErrorHandler( new MyErrorHandler());

        Document doc = db.parse(is);
        return doc;
    }

    public static void writeXml(Node n, OutputStream os)
            throws TransformerException
    {
        TransformerFactory tf = TransformerFactory.newInstance();
        //identity
        Transformer t = tf.newTransformer();
        t.setOutputProperty(OutputKeys.INDENT, "yes");
        t.transform(new DOMSource(n), new StreamResult(os));
    }
    
    public static Document createDocument()
    {
        try
        {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder b = factory.newDocumentBuilder();
            
            return b.newDocument();
        }
        catch (ParserConfigurationException e)
        {
            throw new XFireRuntimeException("Couldn't find a DOM parser.", e);
        }
    }
}
