/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.tomcat.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.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
 *
 * @author Costin Manolache
 */
public class DomUtil {
    private static org.apache.juli.logging.Log log=
        org.apache.juli.logging.LogFactory.getLog( DomUtil.class );

    // -------------------- DOM utils --------------------

    /** 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=DomUtil.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=DomUtil.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=DomUtil.getChild(parent, Node.ELEMENT_NODE);
        if( attVal== null ) {
            while( child!= null &&
                    ( elemName==null || elemName.equals( child.getNodeName())) && 
                    DomUtil.getAttribute(child, attName) != null ) {
                child=getNext(child, elemName, Node.ELEMENT_NODE );
            }
        } else {
            while( child!= null && 
                    ( elemName==null || elemName.equals( child.getNodeName())) && 
                    ! attVal.equals( DomUtil.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
        {
            if( log.isTraceEnabled())
                log.trace("ResolveEntity: " + publicId + " " + systemId);
            return new InputSource(new StringReader(""));
        }
    }

    public static void setAttributes( Object o, Node parent)
    {
        NamedNodeMap attrs=parent.getAttributes();
        if( attrs==null ) return;

        for (int i=0; i<attrs.getLength(); i++ ) {
            Node n=attrs.item(i);
            String name=n.getNodeName();
            String value=n.getNodeValue();

            if( log.isTraceEnabled() )
                log.trace("Attribute " + parent.getNodeName() + " " +
                            name + "=" + value);
            try {
                IntrospectionUtils.setProperty(o, name, value);
            } catch( Exception ex ) {
                ex.printStackTrace();
            }
        }
    }

    /** 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.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 ));
    }
}
