//
// CIMValue.java
//
// The contents of this file are subject to the SNIA Public License Version 1.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.snia.org/resources/openSource.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is CIMValue.java
//
// The Initial Developer of the Original Code is David Simons.
//
// Contributor(s): Michael Leizin, Angela Lee
//
//
// This source uses the Solaris WBEM APIs version 2.2.
// Synchronization will be maintained with the Solaris WBEM APIS,
// which are being standardized through the Java Community Process.
//
// HP Java Client: 1.1  2003/01/09 18:14:30 [ Bapu Patil]
//    - Add support to handle EMPTY XML tags in KEYBINDINGS
//
// $Id: CIMValue.java,v 1.1 2003/07/17 18:32:55 kumpf Exp $
//
package org.snia.wbem.cim;

import java.util.Vector;
import java.lang.reflect.Array;
import java.io.Serializable;
import java.net.MalformedURLException;
import org.snia.wbemcmd.xml.XMLNode;
import org.snia.wbemcmd.xml.XMLClass;

import org.snia.wbemcmd.xml.XMLCIM;

/**
    Creates and instantiates a CIM value, a value of any of the CIM data 
    types that can be assigned to properties, references, and qualifiers.
    A CIM value has a data type (CIMDataType) and the acutal value.   
*/
public class CIMValue extends Object implements Serializable {

    private CIMDataType dataType=null;
    private Object objValue=null;

   /**
     * EMPTY STRING for Exception params.
     */
    public static final String EMPTY = "";

    public static final CIMValue TRUE=new CIMValue(Boolean.TRUE,new CIMDataType(CIMDataType.BOOLEAN));
    public static final CIMValue FALSE=new CIMValue(Boolean.FALSE,new CIMDataType(CIMDataType.BOOLEAN));

    /**
        Constructs values on the basis of the passed object.
	@param Object object of type Vector
	@param CIMDataType  data type of Vector
     */
    public CIMValue(Object o,CIMDataType dt) {
        objValue=o;
        dataType=dt;
        if (dt.isReferenceType() && !(o instanceof CIMObjectPath))
            throw new ExceptionInInitializerError(
                "A ref type must be represented with an object path");
    }

    /**
        Constructs array values on the basis of the passed vector.
	@param Object object of type Vector
	@param CIMDataType  data type of Vector
     */ 
    public CIMValue(Vector v, CIMDataType dt) {
        if (!dt.isArrayType())
            throw new ExceptionInInitializerError(
                "CIMValue created with non array type on wrong constructor ("+v+")");
        objValue=v;
        dataType=dt;
    }

    /**
        Constructs a value on the basis of the passed object -
	object may be a  Vector of primitive types or objects like String, 
	Integer etc 
	@param Object object of type Vector, String or Interger
     */
    public CIMValue(Object o) {

        if (o==null) {
            objValue=null;
            dataType=null;
            return;
        }

        // if it is a Vector, assume (for now) that all elements are
        // of the same type & no array type elements are present
        if (o instanceof Vector) {
            // objValue & dataType are set to null by default
            Vector v = (Vector)o;
            if (! v.isEmpty()) {
                objValue = v;
                Object elementObj = v.elementAt(0);
                // if null, assume String type
                if (elementObj == null)
                    dataType=new CIMDataType(CIMDataType.STRING_ARRAY, v.size());
                else {
                    int simpleType = CIMDataType.findType(elementObj);
                    // if the element object is an invalid type,
                    // throw an Exception
                    if (simpleType == CIMDataType.INVALID) {
                        objValue = null;
                        throw new ExceptionInInitializerError(
                                "Vector element is an invalid type object of "
                                + elementObj.getClass().getName());
                    } else
                        dataType = new CIMDataType(CIMDataType.findArrayType(simpleType), v.size());
                }
            } else {
                throw new ExceptionInInitializerError(
                                        "An empty Vector was passed in");
            }
        } else {
            Class objClass=o.getClass();
            if ( objClass.isArray() ) {
                String arrayClass = objClass.getComponentType().getName();
                try {
                    CIMDataType cdt=CIMDataType.getDataType(arrayClass);
                    dataType=new CIMDataType(
                                   CIMDataType.findArrayType(cdt.getType()) );
                    objValue=o;
                    return;
                } catch (CIMException ce) {
                    throw new ExceptionInInitializerError(ce.toString());
                }
            }
            int type=CIMDataType.findType(o);
            if (type==CIMDataType.INVALID) {
                objValue=null;
                dataType=null;
                throw new ExceptionInInitializerError(
                                        "Failed to determine CIMDataType of "
                                        +objClass.getName());
            }
            dataType=CIMDataType.getPredefinedType(type);
            objValue=o;
        }
    }

    /**
       Create a value using a XML definition
      
       @param node The node containing an XML definition of CIMValue
     */ 
    public CIMValue(org.w3c.dom.Node inNode) 
    {
        if (inNode==null) {
            objValue=null;
            dataType=null;
            return;
        }

        XMLNode node=XMLNode.getXMLNode(inNode);
        int token=node.getToken();
        try 
	{
            //
            // Look for <VALUE> elements
            //
            if (token==XMLNode.XML_ELEMENT_VALUE ||
                token==XMLNode.XML_ELEMENT_KEYVALUE) 
	    {
                objValue=node.getUnescapeText();
		if ( objValue == null )
		{
           	    objValue = EMPTY;
                }
                dataType=CIMDataType.getPredefinedType(CIMDataType.STRING);
                return;
            }
            //
            // Look for <VALUE.ARRAY> elements
            //
            if (token==XMLNode.XML_ELEMENT_VALUE_ARRAY) 
	    {
                Vector v=new Vector();
                XMLNode valueNode=node.getChild();
                while(valueNode!=null) 
		{
                    Object nodeValue=null;
                    if (valueNode.getToken()==XMLNode.XML_ELEMENT_VALUE) 
		    {
			// Before adding to the vector, check for
			//  EMPTY VALUE  
                        // for this, changed the following
			//   v.addElement(valueNode.getUnescapeText());
			// to 
			nodeValue = valueNode.getUnescapeText();
		        if ( nodeValue == null )
         		{
                    	    nodeValue = EMPTY;
                        }
                        v.addElement(nodeValue);
                    }
                    valueNode=valueNode.getNext();
                }
                objValue=v;
                dataType=CIMDataType.getPredefinedType(CIMDataType.STRING_ARRAY);
                return;
            }
            //
            // Look for <VALUE.REFERENCE> elements
            //
            if (token==XMLNode.XML_ELEMENT_VALUE_REFERENCE) {
                XMLNode valueNode=node.getChild();
                token=valueNode.getToken();
                CIMObjectPath cop;
                if (token==XMLNode.XML_ELEMENT_CLASSPATH
                    || token==XMLNode.XML_ELEMENT_LOCALCLASSPATH
                    || token==XMLNode.XML_ELEMENT_INSTANCEPATH
                    || token==XMLNode.XML_ELEMENT_LOCALINSTANCEPATH )
                    cop=new CIMObjectPath(valueNode);
                else if (token==XMLNode.XML_ELEMENT_CLASSNAME) {
                    cop=new CIMObjectPath();
                    cop.setObjectName(valueNode.mustGetAttribute("Name"));
                } else if (token==XMLNode.XML_ELEMENT_INSTANCENAME) {
                    cop=new CIMObjectPath();
                    XMLClass.getInstanceName(valueNode, cop);
                } else
                    throw new CIMException(CIMException.CIM_ERR_FAILED,
                        "Attempting to extract object path");
                objValue=cop;
                dataType=CIMDataType.getPredefinedType(CIMDataType.REFERENCE);
                return;
            }
            //
            // Look for <VALUE.REFARRAY> elements
            //
            if (token==XMLNode.XML_ELEMENT_VALUE_REFARRAY) 
            {
                Vector v=new Vector();
                XMLNode refNode=node.getChild();
                while(refNode!=null) 
                {
                    token=refNode.getToken();
                    if (token==XMLNode.XML_ELEMENT_VALUE_REFERENCE) 
                    {
                        XMLNode valueNode=refNode.getChild();
                        token=valueNode.getToken();
                        CIMObjectPath cop;
                        if (token==XMLNode.XML_ELEMENT_CLASSPATH
                            || token==XMLNode.XML_ELEMENT_LOCALCLASSPATH
                            || token==XMLNode.XML_ELEMENT_INSTANCEPATH
                            || token==XMLNode.XML_ELEMENT_LOCALINSTANCEPATH )
                        {
                            cop=new CIMObjectPath(valueNode);
                        }
                        else if (token==XMLNode.XML_ELEMENT_CLASSNAME)
                        {
                            cop=new CIMObjectPath();
                            cop.setObjectName(valueNode.mustGetAttribute("Name"));
                        } 
                        else if (token==XMLNode.XML_ELEMENT_INSTANCENAME)
                        {
                            cop=new CIMObjectPath();
                            XMLClass.getInstanceName(valueNode, cop);
                        } else
                            throw new CIMException(CIMException.CIM_ERR_FAILED,
                                "Attempting to extract object path");

                        v.addElement(cop);
                    }
                    refNode=refNode.getNext();
                }
                objValue=v;
                Object elementObj = v.elementAt(0);
                // if null, assume String type
                if (elementObj == null)
                    dataType=new CIMDataType(CIMDataType.STRING_ARRAY, v.size());
                else {
                    int simpleType = CIMDataType.findType(elementObj);

                    // if the element object is an invalid type,
                    // throw an Exception
                    if (simpleType == CIMDataType.INVALID) {
                        objValue = null;
                        throw new ExceptionInInitializerError(
                                "Vector element is an invalid type object of "
                                + elementObj.getClass().getName());
                    } else
                        dataType = new CIMDataType(CIMDataType.findArrayType(
                            simpleType), v.size());
                }

                return;
            }
            throw new java.lang.InstantiationError("Not a value node:" + token);
        } 
	catch (CIMException ce) 
	{
            throw new java.lang.InstantiationError(ce.toString());
        }
    }

    /** 
       Determines if the value is an array
       @return TRUE if the value is an array
     */ 
    public boolean isArrayValue() {
        return(dataType.isArrayType());
    }

    /** 
        Returns whether the CIMvalue object is Null.
        Null is the specific condition where no value has
        yet been set into the value.
        If a CIMValue object is Null, any get on that object
        will create an exception.
        @return Returns True if the CIMValue object is Null
    */
    public boolean isNullValue() {
        return(dataType==null);
    }

    /** 
        toMOF - Returns a MOF representation of the CIMValue. 

         @return String representation of this value in Managed Object 
                        Format (MOF).
     */
    public String toMOF() {
        String ret;
        int type;

        if (objValue==null)
            return("");

        type=dataType.getType();
        if (dataType.isArrayType() && objValue instanceof Vector) {
            int i;
            Vector vect=(Vector)objValue;
            type=CIMDataType.findSimpleType(type);

            ret=new String("{");
            for(i=0;i<vect.size();i++) {
                if (i>0)
                     ret=ret.concat(",");
                if (type==CIMDataType.STRING)
                    ret=ret.concat("\"");
                ret=ret.concat(vect.elementAt(i).toString());
                if (type==CIMDataType.STRING)
                    ret=ret.concat("\"");
            }
            ret=ret.concat("}");
            return(ret);
        }
        ret=new String();

        if (type==CIMDataType.STRING)
            ret=ret.concat("\"");
        ret=ret.concat(objValue.toString());

        if (type==CIMDataType.STRING)
            ret=ret.concat("\"");
        return(ret);
    }

    /** 
        toString - Converts the CIMvalue to a string.  Should only be
            used for output purposes.  To get an actual String value, use
            get(String &).
        @return - String output for CIMValue.
        <PRE>
        Example:
            String test;
            CIMValue value(Boolean(true));
            test = value.toString();  // puts "TRUE" into test
        </PRE>
     */
    public String toString() {
        String retVal = "null";
        // return "null" string (not null reference) if valid
        // data is not available

        if ((objValue != null) && (dataType != null)
                 && (dataType.getType() != CIMDataType.INVALID)) {
            int inType=dataType.getType();

            if (inType==CIMDataType.REFERENCE) {
                if (objValue instanceof CIMObjectPath)
                    retVal = objValue.toString();
            } else if (inType==CIMDataType.OBJECT) {
                if (objValue instanceof CIMInstance)
                    retVal = objValue.toString();
            } else if (inType==CIMDataType.CLASS) {
                if (objValue instanceof CIMClass)
                    retVal = objValue.toString();
            } else if (dataType.isArrayType()) {
                Vector v=(Vector)objValue;
                StringBuffer buf = new StringBuffer();
                buf.append("[");
                for(int i=0;i<v.size();i++) {
                    if (v.elementAt(i) != null)
                        buf.append(v.elementAt(i).toString());
                    else
                        buf.append("null");
                    if (i < v.size() - 1)
                        buf.append(", ");
                }
                buf.append("]");
                retVal = buf.toString();
            } else
                retVal = objValue.toString();
        }
        return retVal;
    }

    /**
        Determine if the value vector of this CIMValue contains 
        the specified Object
        @return boolean True if the value vector contains the the 
                        specified object, else false.
     */ 
    public boolean contains(Object obj) {
        return(objValue.equals(obj));
    }


    /**
        Determine if the CIMDataType is empty
        @return boolean True if the cim data type is empty, else false.
     */ 
    public boolean isEmpty() {
        return( dataType==null || objValue==null);
    }

    /**
       Returns size of cim data type
       @return int  size of the data type 
     */
    public int size() {
        if (dataType==null)
            return(0);
        return(dataType.getSize());
    }

    /**
       Gets current value of the this CIMvalue
       @return Object of CIMValue
     */ 
    public Object getValue() {
       return(objValue);
    }

    /**
       Gets current datatype of the this CIMvalue
       @return CIMDataType current CIM datatype
     */ 
    public CIMDataType getType() {
        return(dataType);
    }

    /**
        Determine if the specified Object value equals this CIMValue 
        @return boolean True if the value equal, else false.
     */ 
    public boolean equals(Object obj) {
        CIMValue cv;

        if (!(obj instanceof CIMValue) )
            return(false);

        cv=(CIMValue)obj;

        return(cv.dataType.equals(dataType) &&
               cv.objValue.equals(objValue));
    }

    /** 
        toXml - Converts the CIMvalue to XML representation.  
        @return - String representation of CIMValue.
     */
   public String toXml() throws MalformedURLException {
        //
        // If no value, don't even return a <VALUE> element
        //
        if (objValue==null)
            return("");

        if (dataType.isArrayType()) {
            if (dataType.isReferenceType()) {
                throw new MalformedURLException(
                        "Arrays of references are not valid");
            } else {
                StringBuffer arrayValues=new StringBuffer("<VALUE.ARRAY>");
                if (objValue instanceof Vector) {
                    Vector vect=(Vector)objValue;
                    for(int i=0;i<vect.size();i++) {
                        Object obj = vect.elementAt(i);
                        if( obj == null )
                            arrayValues=arrayValues.append("<VALUE></VALUE>");
                        else
                            arrayValues=arrayValues.append("<VALUE>"
                                +XMLCIM.xmlCIMEscape.XMLEscape(obj.toString())
                                +"</VALUE>");
                    }
                } else {
                    // assume an array
                    int length = Array.getLength(objValue);
                    for(int i=0;i<length;i++) {
                        Object ele=Array.get(objValue,i);
                        if ( ele == null)
                            arrayValues=arrayValues.append("<VALUE></VALUE>");
                        else
                            arrayValues=arrayValues.append("<VALUE>"
                                +XMLCIM.xmlCIMEscape.XMLEscape(ele.toString())
                                +"</VALUE>");
                    }
                }
                arrayValues=arrayValues.append("</VALUE.ARRAY>");
                return(arrayValues.toString());
            }
        } else if (dataType.isReferenceType()) {
            CIMObjectPath refObj=(CIMObjectPath)objValue;
            CIMNameSpace ns=refObj.getFullNameSpace();

            if ( ! ns.getHost().equalsIgnoreCase("NotSet") )
	    {
                return ("<VALUE.REFERENCE><INSTANCEPATH>"+
                    ns.toXml(false)+
                    refObj.toXml()+
                    "</INSTANCEPATH></VALUE.REFERENCE>");
            }
            else if ( ! ns.getNameSpace().equalsIgnoreCase("NotSet") )
	    {
                return ("<VALUE.REFERENCE><LOCALINSTANCEPATH>"+
                    ns.toXml(true)+
                    refObj.toXml()+
                    "</LOCALINSTANCEPATH></VALUE.REFERENCE>");
	    }
            else
	    {
                return ("<VALUE.REFERENCE>"+
                    refObj.toXml()+
                    "</VALUE.REFERENCE>");
            }

        }

        return("<VALUE>"
                    +XMLCIM.xmlCIMEscape.XMLEscape(objValue.toString())
                    +"</VALUE>");
    }
}
