/*
 * $Id: AttributeSet.java,v 1.4 1999/04/13 23:59:55 mode Exp $
 * 
 * Copyright (c) 1998-1999 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 * 
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 */

package com.sun.xml.tree;


import java.io.CharArrayWriter;
import java.io.Writer;
import java.io.IOException;
import java.util.Vector;

import org.w3c.dom.*;

import org.xml.sax.AttributeList;

import com.sun.xml.parser.AttributeListEx;


/**
 * Class representing an XML attribute list.
 *
 * <P> This couples slightly with the Sun XML parser, in that it optionally
 * uses an extended SAX 1.0 API to see if an attribute was specified in the
 * document or was instead defaulted by attribute processing.
 *
 * @author David Brownell
 * @version $Revision: 1.4 $
 */
final
class AttributeSet implements NamedNodeMap, XmlWritable
{
    private boolean	readonly;
    private Vector	list;
    private ElementNode	nameScope;
        
    /* Constructs an attribute list, with associated name scope. */
    // package private
    AttributeSet (ElementNode nameScope)
    {
	list = new Vector (5);
	this.nameScope = nameScope;
    }

    /*
     * Constructs a copy of an attribute list, for use in cloning.
     * name scopes are set separately.
     */
    // package private
    AttributeSet (AttributeSet original, boolean deep)
    {
	int		size = original.getLength ();

	list = new Vector (size);
	for (int i = 0; i < size; i++) {
	    Node	node = original.item (i);

	    if (!(node instanceof AttributeNode))
		throw new IllegalArgumentException (((NodeBase)node).
						getMessage ("A-003"));
	    node = node.cloneNode (deep);

	    // temporarily undo binding to element ... it's rebound
	    // by the caller
	    ((AttributeNode)node).setNameScope (null);
	    list.addElement (node);
	}
    }

    // package private
    AttributeSet (AttributeList source)
    throws DOMException
    {
	int			len = source.getLength ();
	AttributeListEx		ex = null;

	list = new Vector (len);
	if (source instanceof AttributeListEx)
	    ex = (AttributeListEx) source;

	for (int i = 0; i < len; i++) {
	    list.addElement (new AttributeNode (
		    source.getName (i),
		    source.getValue (i),
		    ex == null	// remember if it was specified
			? true
			: ex.isSpecified (i),
		    ex == null	// remember any default value
			? null
			: ex.getDefault (i)
		    ));
	}
	list.trimToSize ();
    }

    // package private
    void trimToSize () { list.trimToSize (); }

    // package private
    public void setReadonly ()
    {
	readonly = true;
	for (int i = 0; i < list.size (); i++)
	    ((AttributeNode)list.elementAt (i)).setReadonly (true);
    }

    public boolean isReadonly () {
    	if (readonly)
	    return true;
	for (int i = 0; i < list.size (); i++) {
	    if (((AttributeNode)list.elementAt (i)).isReadonly ()) {
	   	return true; 
	    }
	}
	return false;
    }

    // package private
    void setNameScope (ElementNode e)
    {
	if (e != null && nameScope != null)
	    throw new IllegalStateException (e.getMessage ("A-004"));
	nameScope = e;

	// need to bind the attributes to this element
	int	length = list.size ();

	for (int i = 0; i < length; i++) {
	    AttributeNode	node;

	    node = (AttributeNode) list.elementAt (i);
	    node.setNameScope (null);
	    node.setNameScope (e);
	}
    }

    // package private
    ElementNode getNameScope ()
    {
	return nameScope;
    }


    // package private
    String getValue (String name)
    {
	Attr	attr = (Attr) getNamedItem (name);

	if (attr == null)
	    return "";
	else
	    return attr.getValue ();
    }

    public Node getNamedItem (String name)
    {
	int	length = list.size ();
	Node	value;

	for (int i = 0; i < length; i++) {
	    value = item (i);
	    if (value.getNodeName ().equals (name))
		return value;
	}
	return null;
    }

    public int getLength ()
    {
	return list.size ();
    }

    public Node item (int index)
    {
	if (index < 0 || index >= list.size ())
	    return null;
	return (Node) list.elementAt (index);
    }

    public Node removeNamedItem (String name)
    throws DOMException
    {
	int		length = list.size ();
	Node		value;

	if (readonly)
	    throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
	for (int i = 0; i < length; i++) {
	    value = item (i);
	    if (value.getNodeName ().equals (name)) {
		AttributeNode	att = (AttributeNode) value;

		if (att.getDefaultValue () != null) {
		    att = new AttributeNode (att);
		    att.setOwnerDocument ((XmlDocument)
			    nameScope.getOwnerDocument ());
		    list.setElementAt (att, i);
		} else
		    list.removeElementAt (i);

		att.setNameScope (null);
		return value;
	    }
	}
	throw new DomEx (DomEx.NOT_FOUND_ERR);
    }

    public Node setNamedItem (Node value)
    throws DOMException
    {
	AttributeNode	node;

	if (readonly)
	    throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
	if (!(value instanceof AttributeNode)
		|| value.getOwnerDocument ()
			!= nameScope.getOwnerDocument ())
	    throw new DomEx (DomEx.WRONG_DOCUMENT_ERR);
	node = (AttributeNode)value;
	if (node.getNameScope () != null)
	    throw new DomEx (DomEx.INUSE_ATTRIBUTE_ERR);

	int		length = list.size ();
	AttributeNode	oldValue;

	for (int i = 0; i < length; i++) {
	    oldValue = (AttributeNode) item (i);
	    if (oldValue.getNodeName ().equals (value.getNodeName ())) {
	    	if (oldValue.isReadonly ())
	    	    throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
		node.setNameScope (nameScope);
		list.setElementAt (value, i);
		oldValue.setNameScope (null);
		return oldValue;
	    }
	}
	node.setNameScope (nameScope);
	list.addElement (value);
	return null;
    }
    
    /**
     * Writes out the attribute list.  Attributes known to have been
     * derived from the DTD are not (at this time) written out.  Part
     * of writing standalone XML is first ensuring that all attributes
     * are flagged as being specified in the "printed" form (or else
     * are defaulted only in the internal DTD subset).
     */
    public void writeXml (XmlWriteContext context) throws IOException
    {
	Writer		out = context.getWriter ();
	int		length = list.size ();
	AttributeNode	tmp;

	for (int i = 0; i < length; i++) {
	    tmp = (AttributeNode) list.elementAt (i);
	    if (tmp.getSpecified ()) {
		out.write (' ');
		tmp.writeXml (context);
	    }
	}
    }

    /**
     * Does nothing; this type of node has no children.
     */
    public void writeChildrenXml (XmlWriteContext context) throws IOException
    {
    }

    public String toString ()
    {
	try {
	    CharArrayWriter w = new CharArrayWriter ();
	    XmlWriteContext x = new XmlWriteContext (w);
	    writeXml (x);
	    return w.toString ();

	} catch (IOException e) {
	    return super.toString ();
	}
    }
}
