/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/util/org/apache/slide/util/JDom.java,v 1.5 2004/07/30 06:52:17 ozeigermann Exp $
 * $Revision: 1.5 $
 * $Date: 2004/07/30 06:52:17 $
 *
 * ====================================================================
 *
 * 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.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.util.List;
import org.apache.slide.util.os.Platform;
import org.jdom.Attribute;
import org.jdom.Comment;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.ProcessingInstruction;
import org.jdom.Text;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

/**
 ** @author michael.hartmeier@softwareag.com
 ** @version $Revision: 1.5 $
 **/
public class JDom {
    public static final String UTF_8 = "UTF-8";
    
    // TODO: better name
    public static Element getXElement(Element element, String name) {
        Element result;
        
        result = element.getChild( name );
        if (result == null) {
            throw new XAssertionFailed("missing element '" + name + "' in element '" + element.getName() +"'" );
        }
        return result;
    }
    
    public static Element getElement(Element element, String name) throws XException {
        List nodes;
        
        nodes = element.getChildren( name );
        switch ( nodes.size() ) {
            case 0:
                throw new XException("missing element '" + name + "' in element '" + element.getName() +"'" );
            case 1:
                return (Element) nodes.get(0);
            default:
                throw new XException("exactly one object node expected for data element");
        }
    }
    
    public static String getAttribute(Element element, String name) throws XException {
        String value;
        
        value = element.getAttributeValue( name );
        if (value == null) {
            throw new XException("missing attribute '" + name + "' in element '" + element.getName() + "'");
        }
        return value;
    }
    
    public static String getAttribute(Element element, String name, String dflt) {
        String value;
        
        value = element.getAttributeValue( name );
        if (value == null) {
            return dflt;
        } else {
            return value;
        }
    }
    
    public static String print(String result, OutputStream out) throws XException {
        try {
            out.write( result.getBytes(UTF_8) );
        }
        catch( IOException x ) {
            throw new XException( "Could not output XML string to given writer:\n"+result, x );
        }
        return result;
    }
    
    public static XMLOutputter outputter() {
        Format fmt;
        
        fmt = Format.getPrettyFormat();
        fmt.setIndent("  ");
        fmt.setEncoding(JDom.UTF_8);
        fmt.setTextMode(Format.TextMode.NORMALIZE);
        fmt.setLineSeparator(Platform.CURRENT.lineSeparator);
        return new XMLOutputter(fmt);
    }
    
    public static String toString(Document doc) {
        return outputter().outputString( doc );
    }
    
    public static String toString(Element ele) {
        return outputter().outputString( ele );
    }
    
    public static Document forString(String str) throws JDOMException, IOException {
        SAXBuilder builder;
        Document doc;
        
        builder = new SAXBuilder();
        doc = builder.build(new StringReader(str));
        return doc;
    }
    
    public static Document forFile(File file) throws XException {
        try {
            return forInputStream(new FileInputStream(file));
        } catch (FileNotFoundException e) {
            throw new XException("File " + file + " not found" );
        } catch (IOException x) {
            throw new XException( "Parsing '" + file + "' failed:" + x.getMessage(), x );
        } catch (JDOMException x) {
            throw new XException( "Parsing '" + file + "' failed:" + x.getMessage(), x );
        }
    }
    
    public static Document forResource(String resourceName) throws JDOMException, IOException {
        return forInputStream(JDom.class.getResourceAsStream(resourceName));
    }
    
    public static Document forInputStream(InputStream in) throws JDOMException, IOException {
        SAXBuilder builder;
        Document doc;
        
        builder = new SAXBuilder();
        doc = builder.build(in);
        return doc;
    }
    
    //-- equals
    
    /**
     ** Compares two elements. This method is necessary because Element.equals()
     ** and Attribute.equals() in JDOM beta 6 compare pointers only.
     **
     ** @param left     first element for the operation.
     ** @param right    second element for the operation.
     **
     ** @return true if the elements are equal.
     **/
    public static boolean equals(Element left, Element right) {
        if (!left.getName().equals(right.getName())) {
            return false;
        }
        if (!left.getNamespace().equals(right.getNamespace())) {
            return false;
        }
        if (!equalsContent(left.getChildren(), right.getChildren())) {
            return false;
        }
        if (!containsAttributes(left.getAttributes(), right.getAttributes())) {
            return false;
        }
        if (!containsAttributes(right.getAttributes(), left.getAttributes())) {
            return false;
        }
        return true;
    }
    
    /**
     ** Compares two attributes.
     **
     ** @param left     first element for the operation.
     ** @param right    second element for the operation.
     **
     ** @return true if the attributes are equal.
     **/
    public static boolean equals(Attribute left, Attribute right) {
        return left.getName().equals(right.getName())
            && left.getValue().equals(right.getValue())
            && left.getNamespace().equals(right.getNamespace());
    }
    
    /**
     ** Compares two lists of element content.
     **
     ** @param lefts     first list for the operation.
     ** @param rights    second list for the operation.
     **
     ** @return true if the lists have the same number of elements and
     **         each element at pos i within the first list equals the
     **         the element at pos i within the second list.
     **
     ** @pre    lefts != null
     ** @pre    rights != null
     ** @pre    for all x in (lefts + rights).
     **             x instanceof Element
     ** @post   for all i in [0, lefts.size()-1].
     **             lefts.get(i).equals(rights.get(i))
     **/
    public static boolean equalsContent(List lefts, List rights) {
        int i;
        Object a;
        Object b;
        ProcessingInstruction pA;
        ProcessingInstruction pB;
        
        
        // precondition
        if (lefts == null) {
            throw new XAssertionFailed("list parameter 'lefts' must not be null");
        }
        if (rights == null) {
            throw new XAssertionFailed("list parameter 'rights' must not be null");
        }
        
        if (lefts.size() != rights.size()) {
            return false;
        }
        for (i = 0; i < lefts.size(); i++) {
            a = lefts.get(i);
            b = rights.get(i);
            if (a instanceof Element) {
                if (!(b instanceof Element)) {
                    return false;
                }
                if (!equals((Element) a, (Element) b)) {
                    return false;
                }
            } else if (a instanceof Text) {
                if (!(b instanceof Text)) {
                    return false;
                }
                if (!((Text) a).getText().equals(((Text) b).getText())) {
                    return false;
                }
            } else if (a instanceof Comment) {
                if (!(b instanceof Comment)) {
                    return false;
                }
                if (!((Comment) a).getText().equals(((Comment) b).getText())) {
                    return false;
                }
            } else if (a instanceof ProcessingInstruction) {
                pA = (ProcessingInstruction) a;
                pB = (ProcessingInstruction) b;
                if ((pA.getTarget() == null && pB.getTarget() != null)
                    || !pA.getTarget().equals(pB.getTarget())) {
                    return false;
                }
                if ((pA.getData() == null && pB.getData() != null)
                    || !pA.getData().equals(pB.getData())) {
                    return false;
                }
            } else {
                throw new UnsupportedOperationException("content not supported: " + a.getClass());
            }
            
            if (!equals((Element) lefts.get(i), (Element) rights.get(i))) {
                return false;
            }
        }
        return true;
    }
    
    /**
     ** Compares two lists of attributes and checks whether the second list is
     ** a subset of the first list.
     **
     ** @param all      list of attributes.
     ** @param parts    list of attributes.
     **
     ** @return true if for each attribute in parts exists an equal attibute
     **         in parts.
     **
     ** @pre    all != null
     ** @pre    parts != null
     ** @pre    for all x in parts.
     **             x instanceof Attribute
     ** @post   for all x in parts.
     **             not for all y in all.
     **                 not x.equals(y)
     **/
    private static boolean containsAttributes(List all, List parts) {
        int i;
        int j;
        Attribute part;
        
        if (parts.size() > all.size()) {
            return false;
        }
        for (i = 0; i < parts.size(); i++) {
            part = (Attribute) parts.get(i);
            for (j = 0; j < all.size(); j++) {
                if (equals(part, (Attribute) all.get(j))) {
                    break;
                }
            }
            if (j == all.size()) {
                // not found
                return false;
            }
        }
        return true;
    }
}


