/*
 * (C) Copyright IBM Corp. 1997-1998  All rights reserved.
 *
 * US Government Users Restricted Rights Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *
 * The program is provided "as is" without any warranty express or
 * implied, including the warranty of non-infringement and the implied
 * warranties of merchantibility and fitness for a particular purpose.
 * IBM will not be liable for any damages suffered by you as a result
 * of using the Program. In no event will IBM be liable for any
 * special, indirect or consequential damages or lost profits even if
 * IBM has been advised of the possibility of their occurrence. IBM
 * will not be liable for any third party claims against you.
 */

package com.ibm.xml.parser;

import org.w3c.dom.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.UnsupportedEncodingException;

/**
 * The TXAttribute class implements the Attribute interface as defined by the Document Object Model (DOM),
 * and implements the namespace interface as defined by the W3C.
 * <p>Attribute Nodes represent an attribute in an Element object; in other words, an
 * attribute name and an attribute object.  Typically the allowable values for the 
 * attribute are defined in a document type definition.  
 * <p>The attribute's effective value is determined as follows: if the attribute has been 
 * explicitly assigned any value, that value is the attribute's effective value.  Otherwise, 
 * if there is a declaration for this attribute, and that declaration includes a default 
 * value, then that default value is the attribute's effective value.  Otherwise, the 
 * attribute has no effective value.  
 * <p> In XML, the value of an attribute is represented by a list because
 * the value can be an arbitrarily complex list of entity references. 
 *
 * @version 1.1
 * @author TAMURA Kent &lt;kent@trl.ibm.co.jp&gt;
 * @see org.w3c.dom.Attribute
 * @see com.ibm.xml.parser.Namespace
 * @see com.ibm.xml.parser.Child
 */
public class TXAttribute extends Child implements Attribute, Namespace {

            String      name            = null;
            String      value           = null;
            int         type            = AttDef.UNKNOWN; 
            String[]    typedValue      = null; // typeValue[0] = value
            boolean     specified       = false;

            String      nsLocalName     = null;
            String      nsURI           = null;
    
    /**
     * Constructor.
     * @param name      The name of this Attribute. 
     * @param data      The string value of this Attribute.
     */
    public TXAttribute(String name, String value) {
        this.name = name;
        this.value = value;
        this.specified = true;
    }

    /**
     * Clone this TXAttribute Node using the appropriate factory.  Note that this
     * method does not clone Parent or sibling Nodes.
     * <p>This method is defined by Child.
     * @return          Cloned TXAttribute Node.
     * @see com.ibm.xml.parser.Child#clone
     */
    public synchronized Object clone() {
        checkFactory();
        TXAttribute a = factory.createAttribute(this.name, this.value);
        a.setFactory(getFactory());
        //a.setNSPrefixName(getNSPrefixName());
        a.setNSLocalName(getNSLocalName());
        a.setNSName(getNSName());
        return a;
    }
    
    /**
     * Returns that this object is an Attribute Node.
     * <p>This method is defined by DOM.
     * @return          Attribute Node indicator.
    */
    public int getNodeType() {
        return Node.ATTRIBUTE;
    }

    /**
     * Returns the qualified name of this Attribute.  If the attribute name has a namespace prefix, 
     * the prefix will still be attached.
     * <p>This method is defined by DOM.
     * @return          The name of this Attribute (should never be null)
     */
    public String getName() {
        return this.name;
    }

    /**
     * Returns the value of this Attribute.
     * <p>This method is defined by DOM.
     * @return          The value of this Attribute, or "" if no value.
     * @see #getTypedValue
     * @see #setValue
     */
    public String getValue() {
        return this.value;
    }

    /**
     * Sets the value of this Attribute.
     * @param value     The value of this Attribute.
     * @see #getValue
     * @see #getTypedValue
     */
    public void setValue(String value) {
        this.value = value;
        clearDigest();
    }

    /**
     * Returns whether this Attribute was explicitly given a value in the original 
     * document (=true), or was allowed to default according to the DTD specification.
     * @return          =true if value explicitly defined; otherwise, false.
     * @see #setSpecified
     */
    public boolean getSpecified() {
        return this.specified;
    }

    /**
     * Sets whether this Attribute was explicitly given a value in the original 
     * document (=true), or was allowed to default according to the DTD specification.
     * @param   specified   =true if value explicitly defined; otherwise, false.
     * @see #getSpecified
     */
    public void setSpecified(boolean specified) {
        this.specified = specified;
    }

    /**
     * Returns the local name of the Namespace.
     * <p>This method is defined by Namespace.
     * @return          The Namespace local name, or <var>null</var> if no local name. 
     * @see com.ibm.xml.parser.Namespace
     * @see #setNSLocalName
     */
    public String getNSLocalName() {
        return this.nsLocalName;
    }
    
    /**
     * Sets the local name of the Namespace.  
     * <p>This method is defined by Namespace.
     * @param   nsLocalName The Namespace local name.
     * @see com.ibm.xml.parser.Namespace
     * @see #getNSLocalName
     */
    public void setNSLocalName(String nsLocalName) {
        this.nsLocalName = nsLocalName;
    }
    
    /**
     * Returns the Namespace URI.  
     * <p>This method is defined by Namespace.
     * @return          The Namespace URI, or <var>null</var> if the tag name does not
     *                  contain a prefix name or XML4J parser namespace processing is disabled.
     * @see #setNSName 
     * @see com.ibm.xml.parser.Parser#setProcessNamespace
     * @see com.ibm.xml.parser.Namespace
     */
    public String getNSName() {
        return this.nsURI;
    }
    /**
     * Sets the Namespace URI.  
     * <p>This method is defined by Namespace.
     * @param   nsURI   The Namespace URI, or <var>null</var> if no name.
     * @see #getNSName 
     * @see com.ibm.xml.parser.Namespace
     */
    public void setNSName(String nsURI) {
        this.nsURI = nsURI;
    }

    /**
     * If namespace support is enabled and this Attribute's name contains a prefix name, return a string 
     * of <CODE>getNSName()+":"+getNSLocalName()</CODE>; otherwise, return <CODE>getNSLocalName()</CODE> .
     * @return          The universal name, or <var>null</var> if this Attribute's name does not
     *                  contain a prefix name or XML4J parser namespace processing is disabled.
     * @see #getNSName
     * @see #getNSLocalName
     * @see com.ibm.xml.parser.Parser#setProcessNamespace
     */
    public String getUniversalName() {
        return getNSName() == null ? getNSLocalName() : getNSName()+":"+getNSLocalName();
    }

    /**
     * Returns the type of the Attribute.  Attribute types are only set when
     * a grammar definition (DTD) is available.
     * @return          The Attribute type as defined by AttDef.Java, or <var>null</var> when no type.
     * @see com.ibm.xml.parser.AttDef
     * @see #setType
     * @see #getTypedValue
     */ 
    public int getType() {
        return this.type;
    }

    /**
     * Returns the type values of the Attribute.  Attribute type values are only set when
     * a grammar definition (DTD) is available.
     * @return          <CODE>getTypeValue()[0] = getValue()</CODE> when <CODE>getType() = CDATA, NOTATION, ID, IDREF, ENTITY, NMTOKEN</CODE>;
     *                    otherwise, multiple values are returned.
     * @see com.ibm.xml.parser.AttDef
     * @see #setType
     * @see #getType
     */
    public String[] getTypedValue() {
        return this.typedValue;
    }

    /**
     * Sets the type and type values of the Attribute.  Attribute types are only set when
     * a grammar definition (DTD) is available.
     * @param   type        The Attribute type as defined by AttDef.Java.
     * @param   typedValue  <CODE>getTypeValue()[0] = getValue()</CODE> when <CODE>getType() = CDATA, NOTATION, ID, IDREF, ENTITY, NMTOKEN</CODE>;
     *                        otherwise, multiple values are returned.
     * @see com.ibm.xml.parser.AttDef
     * @see #setType
     * @see #getType
     * @see #getTypedValue
     */
    public void setType(int type, String[] typedValue) {
        this.type       = type;
        this.typedValue = typedValue;
        this.value      = normalize(type, this.value);
    }

    /**
     * Returns this Node in XML format, in <code>name="value"</code> format,
     * and using the default character encoding.
     * Entities embedded in the returned string will be respected.
     * For example, the characters <code>&lt;,  &gt;,  &amp;,  &apos;, and &quot; </code> 
     * will be represented as <code>&amp;lt;, &amp;gt;, &amp;amp;, &amp;apos;, &amp;quot; </code>. 
     * @return          XML formatted string expressed as <code>name="value"</code>.
     * @see #getText
     * @see com.ibm.xml.parser.Entity
     */
    public String toXMLString() {
        return toXMLString(null);
    }

    /**
     * Returns this Node in XML format, in <code>name="value"</code> format,
     * and using the specified character encoding.
     * Entities embedded in the returned string will be respected.
     * For example, the characters <code>&lt;,  &gt;,  &amp;,  &apos;, and &quot; </code> 
     * will be represented as <code>&amp;lt;, &amp;gt;, &amp;amp;, &amp;apos;, &amp;quot; </code>. 
     * @param enc       Java character encoding to use.
     * @return          XML formatted string expressed as <code>name="value"</code>.
     * @see #getText
     * @see com.ibm.xml.parser.Entity
     */
     // Why does this class have an encoding-sensitive version of toString, and others do not? (TXText, TXElement)
    public String toXMLString(String enc) {
        return getName()+"=\""+Util.backReference(this.value, enc)+"\"";
    }

    /**
     * Returns this Node in XML format, in <code>name="value"</code> format,
     * and using the default character encoding.
     * Entities embedded in the returned string will be respected.
     * For example, the characters <code>&lt;,  &gt;,  &amp;,  &apos;, and &quot; </code> 
     * will be represented as <code>&amp;lt;, &amp;gt;, &amp;amp;, &amp;apos;, &amp;quot; </code>. 
     * @return          XML formatted string expressed as <code>name="value"</code>.
     * @see #toXMLString
     * @see com.ibm.xml.parser.Entity
     */
    public String getText() {
        return toXMLString();
    }

    /**
     * Returns whether the specified <var>object</var> matches this Attribute Node.  
     * A match is defined when the specified <var>object</var>'s name matches the name 
     * of this Attribute.
     * @return          =true if the names match; otherwise, false.
     */
    public boolean equals(Object object) {
        if (null == object)  return false;
        if (object instanceof Attribute) {
            return getName().equals(((Attribute)object).getName());
        } else if (object instanceof String) {
            return getName().equals((String)object);
        } else
            return false;
    }
    
    /**
     * Normalize the specified String by trimming leading and trailing space, and
     * removing superfluous embedded spaces.
     * @return          Normalized string.
     * @see #toXMLString
     */
    static String normalize(int type, String src) {   // S->&#x20 conversion is done.
        if (AttDef.UNKNOWN == type || AttDef.CDATA == type)  return src;
        src = src.trim();
        StringBuffer sb = new StringBuffer(src.length());
        boolean previousSpace = false;
        for (int i = 0;  i < src.length();  i ++) {
            char ch = src.charAt(i);
            if (' ' == ch) {
                if (!previousSpace)  sb.append(ch);
                previousSpace = true;
            } else {
                previousSpace = false;
                sb.append(ch);
            }
        }
        return sb.toString();
    }
}
