// 
// The Waveset SPML General License 
// 
// Version 0.1, April 2003
// Copyright (C) 2003 Waveset Technologies, Inc..
// 6034 West Courtyard Drive, Suite 210, Austin, Texas 78730
// All rights reserved.
// 
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// 
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions, and the disclaimers in Sections 6
//    and 7 below.
// 
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the disclaimers in Sections 6
//    and 7 below in the documentation and/or other materials provided
//    with the distribution.
// 
// 3. The end-user documentation included with the redistribution, if
//    any, must include the following acknowledgment:
// 
//    "This product includes software developed by 
//     Waveset Technologies, Inc. (www.waveset.com)."
// 
//    Alternately, this acknowledgment may appear in the software itself, if
//    and wherever such third-party acknowledgments normally appear.
//  
// 4. The names "Waveset" and "Waveset Technologies, Inc." must not be
//    used to endorse or promote products derived from this software
//    without the prior written permission of Waveset. For written
//    permission, please contact www.waveset.com.
//  
// 5. Products derived from this software may not be called "Waveset",
//    nor may "Waveset" appear in their name, without the prior written
//    permission of Waveset.
//  
// 6. NO WARRANTY 
// 
//    THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED
//    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//    DISCLAIMED.
// 
// 7.  LIMITATION OF LIABILITY
// 
//    IN NO EVENT SHALL THE WAVESET OR ITS LICENSORS BE LIABLE FOR ANY
//    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
//    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
//    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
//    IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
//    OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
//    ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
// End of Terms and Conditions
// ====================================================================
//
// Component Name: org.openspml.message.Attribute
//
// Author(s): Jeff Larson
//
// Description:
//
// Memory representation for an SPML (and DSML) <attr> element.
// Since this is nothing more than name/value it would be possible
// to just use a Map, but keep it a distinct class in case we need
// to add something more to this.  This also serves as a convenient
// superclass for Modification.
//
//

package org.openspml.message;

import java.util.*;
import org.openspml.util.*;

/**
 * A class used to represent an <code>attr</code> element, found
 * in several contexts within SPML.
 * <p>
 * Currently this is the same as a DSML attribute, though it is
 * likely to be extended to provide a richer set of value types.
 * DSML only allows xsd:string, xsd:base64binary and xsd:anyURI values.
 * <p>
 * The value is allowed to be any object, but until an extension
 * mechanism is in place, values should be limited to Strings or
 * Lists of Strings.  Multi-valued attributes are represented
 * with Lists.
 */
public class Attribute {

    //////////////////////////////////////////////////////////////////////
    //
    // Fields
    //
    //////////////////////////////////////////////////////////////////////

    static final String ELEMENT = "attr";

    /**
     * Required attribute name.
     */
    String _name;

    /**
     * Attribute value, may be a List.
     */
    Object _value;

    //////////////////////////////////////////////////////////////////////
    //
    // Construtors
    //
    //////////////////////////////////////////////////////////////////////

    public Attribute() {
    }

    Attribute(XmlElement e) {
        parseXml(e);
    }

    public Attribute(String name, Object value) {
        _name = name;
        _value = value;
    }

    public void setName(String s) {
        _name = s;
    }

    public void setValue(Object o) {
        _value = o;
    }
    
    /**
     * Convenience method to build a List of Attribute objects
     * given a Map of name/value pairs.  
     */
    public static List getAttributes(Map src) {

        List list = null;
        if (src != null) {
            Iterator it = src.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry entry = (Map.Entry)it.next();
                String name = (String)entry.getKey();
                Object value = entry.getValue();
                if (name != null) {
                    Attribute a = new Attribute(name, value);
                    if (list == null)
                        list = new ArrayList();
                    list.add(a);
                }
            }
        }
        return list;
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Accessors
    //
    //////////////////////////////////////////////////////////////////////

    public String getName() {
        return _name;
    }

    public Object getValue() {
        return _value;
    }

    /**
     * Utility method to Lookup an attribute by name given a list
     * of attributes.  Used by several classes to search their
     * attribute lists.
     * <p>
     // It is assumed this won't happen very often and the lists will
     // be short.  If not, then an intermediate class that maintains
     // a HashMap may be required.
     */
    public static Attribute getAttribute(List attributes, String name) {

	Attribute found = null;
	if (attributes != null) {
	    for (int i = 0 ; i < attributes.size() ; i++) {
		Attribute a = (Attribute)attributes.get(i);
		if (name.equals(a.getName())) {
		    found = a;
		    break;
		}
	    }
	}
	return found;
    }

    /**
     * Utility method to Lookup an attribute by name given a list
     * of attributes.  Used by several classes to search their
     * attribute lists.
     * <p>
     * The actual value of the attribute is returned.
     */
    public static Object getAttributeValue(List attributes, String name) {
	Object value = null;
	Attribute att = getAttribute(attributes, name);
	if (att != null)
	    value = att.getValue();
	return value;
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Sorting
    //
    //////////////////////////////////////////////////////////////////////

    /**
     * Utility to sort a list of Attribute objects by name.
     * Convenient for unit tests cause the attributes to serialize
     * in a predictable order.
     */
    public static void sort(List list) {

        if (list != null) 
            Collections.sort(list, new AttributeComparator());
    }

    /**
     * Internal class that implenents the Comparator interface
     * for sorting Attributes.
     */
    private static class AttributeComparator implements java.util.Comparator {

	public AttributeComparator() {
	}

	public int compare(Object o1, Object o2) {

	    int cmp = 0;

            Attribute att1 = (Attribute)o1;
            Attribute att2 = (Attribute)o2;

            Object name1 = att1.getName();
            Object name2 = att2.getName();

            if (name1 != null && name2 != null)
                cmp = name1.toString().compareTo(name2);
            else if (name1 == null && name2 == null)
                cmp = 0;
            else if (name1 == null)
                cmp = -1;
            else
                cmp = 1;

	    return cmp;
	}

	public boolean equals(Object obj) {
            return (obj instanceof AttributeComparator);
	}
    }

    //////////////////////////////////////////////////////////////////////
    //
    // XML
    //
    //////////////////////////////////////////////////////////////////////

    /**
     * This is overloaded by Modification to return a different element name.
     */
    String getElementName() {
        return ELEMENT;
    }
    
    void addSubclassAttributes(SpmlBuffer b) {
    }

    void toXml(SpmlBuffer b) {

        // there is currently no way to distinguish between
        // null and an empty list
        List list = null;
        if (_value instanceof List)
            list = (List)_value;

        boolean empty = (_value == null || (list != null && list.size() == 0));
        
        String tag = getElementName();
	
        // Note that there were corrections in the spec to properly
        // add the dsml namespace for <attr> and <modification> elemenets
        // after the schema was published

        b.addOpenStartTag("dsml", tag);
        b.addAttribute("name", _name);

        // Modification overloads this to add operation type
        addSubclassAttributes(b);

	if (empty)
	    b.closeEmptyElement();
	else {
	    b.closeStartTag();
	    b.incIndent();

	    // assume these are just strings for now
            if (list == null)
                 toXmlValue(b, _value);
             else {
                 Iterator it = list.iterator();
                 while (it.hasNext())
                     toXmlValue(b, it.next());
             }

            b.decIndent();
            b.addEndTag("dsml", tag);
        }
    }

    static private void toXmlValue(SpmlBuffer b, Object value) {

	if (value != null) {
	    // NOTE: These are in the dsml namespace, not spml
            // !! Now that the parent <dsml:attr> has the dsml prefix, 
            // can we avoid the previx here?
	    b.addStartTag("dsml", "value", false);
	    b.addContent(value.toString());
	    b.addEndTag("dsml", "value", false);
	}
    }

    /**
     * Emit <attr> syntax for something that isn't represented
      * as an Attribute in memory.
      */
    static void toXml(SpmlBuffer b, String name, Object value) {

        // Note that this was fixed to use the dsml: namespace
        // after the 1.0 schema was published.

	b.addOpenStartTag("dsml", ELEMENT);
	b.addAttribute("name", name);
	b.closeStartTag();
	b.incIndent();

	toXmlValue(b, value);

	b.decIndent();
	b.addEndTag("dsml", ELEMENT);
    }

    /**
      * Emit a wrapper element containing a list of <attr> 
      * elements for name/value pairs represented in a Map.
      */
    static void toXml(SpmlBuffer b, String wrapper, Map attributes) {

	if (attributes != null && attributes.size() > 0) {
	    b.addStartTag(wrapper);
	    b.incIndent();
	    Iterator it = attributes.entrySet().iterator();
	    while (it.hasNext()) {
		Map.Entry entry = (Map.Entry)it.next();
		toXml(b, (String)entry.getKey(), entry.getValue());
	    }
	    b.decIndent();
	    b.addEndTag(wrapper);
	}
    }

    /**
     * Emit a list of <attr> elements surrounded by a wrapper elemenet.
     */
    static void toXml(SpmlBuffer b, String wrapper, List attributes) {

        if (attributes != null && attributes.size() > 0) {
            b.addStartTag(wrapper);
            b.incIndent();

            Iterator it = attributes.iterator();
            while (it.hasNext()) {
                Attribute att = (Attribute)it.next();
                att.toXml(b);
            }

            b.decIndent();
            b.addEndTag(wrapper);
        }
    }

    /**
      * Parse one Attribute definition.
      */
    void parseXml(XmlElement e) {

        _name = e.getAttribute("name");

        // children can only be <value>, assume for now they're just strings
        List values = null;

        for (XmlElement child = e.getChildElement() ; child != null ; 
             child = child.next()) {

            String value = child.getContent();
            if (values != null)
                values.add(value);
            else if (_value == null) 
                _value = value;
            else {
                // promote to a List
                values = new ArrayList();
                values.add(_value);
                values.add(value);
                _value = values;
            }
        }
    }

    /**
     * Parse a list of Attribute or Modification objects.
     */
    static List parseList(XmlElement wrapper) {

        List list = null;
        XmlElement el = wrapper.getChildElement();
        if (el != null) {
            list = new ArrayList();
            for ( ; el != null ; el = el.next()) {
                String tag = el.getLocalName();
                if (tag.equals(Modification.ELEMENT))
                    list.add(new Modification(el));
                else 
                    list.add(new Attribute(el));
            }
        }
	return list;
    }

    /**
     * Parse a list of <attr> elements into a Map.
     * For those that want to maintain a simple map
     * rather than blowing out Attribute objects.
     */
    static Map parseMap(XmlElement wrapper) {

        Map map = null;
        XmlElement el = wrapper.getChildElement();
        if (el != null) {
            map = new HashMap();
            for ( ; el != null ; el = el.next()) {
                String tag = el.getLocalName();
		// should be smarter here and generate
		// less garbage, only allow Attribute
		// since Modification carries more state

                Attribute a = new Attribute(el);
		map.put(a.getName(), a.getValue());
	    }
        }
	return map;
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Static utilities 
    //
    //////////////////////////////////////////////////////////////////////

    /**
     * Given a list of Attribute objects, convert them into a 
     * Map of names and values.
     */
    public static Map toMap(List attlist) {
        
        Map map = null;
        if (attlist != null && attlist.size() > 0) {
            map = new HashMap();
            Iterator it = attlist.iterator();
            while (it.hasNext()) {
                Attribute att = (Attribute)it.next();
                String name = att.getName();
                if (name != null)
                    map.put(name, att.getValue());
            }
        }
        return map;
    }

}

