//
// CIMProperty.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 CIMProperty.java
//
// The Initial Developer of the Original Code is David Simons.
//
// Contributor(s):
//
//
// 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.
//
//
// $Id: CIMProperty.java,v 1.1 2003/07/17 18:32:55 kumpf Exp $
//
//
package org.snia.wbem.cim;

import java.net.MalformedURLException;
import java.util.Vector;
import java.io.Serializable;
import java.io.StringReader;
import java.util.Date;
import java.lang.reflect.Array;
import org.snia.wbemcmd.xml.XMLNode;
import org.snia.wbemcmd.xml.XMLParameters;
import org.snia.wbemcmd.xml.XMLParse;
import java.util.GregorianCalendar;
import java.util.Calendar;
import org.snia.wbemcmd.cimom.CIMOMCast;

/**
    Creates and instantiates a CIM property, a name/value pair used to
    characterize an instance of a CIM class.  Properties can be 
    thought of as a pair of functions, one to set the property value,
    and one to return the property value.  A CIM Property is defined
    by its name and origin class.  Two or more properties can have the
    same name, but the origin class uniquely identifies the property.
 */

public class CIMProperty  extends CIMElement implements Cloneable, Serializable {
    
    // Bapu ( why empty?, 08/07/02)
    // I believe as per the SPEC if the property includes CLASSORIGIN, 
    // it should be set to a valid class, not EMPTY.  
    // This is fully checked by Pegasus-CIMOM.
    //
    static private String noOriginClass="";
    
    //
    // If you add to these, you need to fix the clone() method!
    //
    Vector qualifiers=new Vector();
    //
    // Note that we can't rely on the cimValue's datatype
    // because that is determined by what gets stored  as
    // a value rather than what the MOF declared which is
    // what is stored in propertyDataType.
    //
    CIMDataType propertyDataType=null;
    //
    // For an array type property this states how large it
    // was declared as
    //
    int sizeDataType= -1;
    String override=null;
    String originClass=noOriginClass;
    CIMValue cimValue=null;
    boolean propagated=false;       // propagated means inherited without change

     /** CIMProperty constructor. */
    public CIMProperty() {
    }

    /** 
	CIMProperty constructor creates a property with a name 
	@param String name of the property
     */
    public CIMProperty(String name) {
        setName(name);
    }

    /** 
        CIMProperty constructor creates a property with a name and value
	@param String name of the property
	@param CIMValue value of the property
     */
    public CIMProperty(String name,CIMValue val) {
        setName(name);
        cimValue=val;
        propertyDataType=val.getType();
    }

    /** 
        CIMProperty constructor creates a property a XML definition
        @param node The node containing an XML definition of CIMProperty
     */
    public CIMProperty(org.w3c.dom.Node node) {
        XMLNode qnode=XMLNode.getXMLNode(node);
        String superClassName;
        String inClassName;

        int token=qnode.getToken();
        if (token!=XMLNode.XML_ELEMENT_PROPERTY &&
            token!=XMLNode.XML_ELEMENT_PROPERTY_ARRAY &&
            token!=XMLNode.XML_ELEMENT_PROPERTY_REF )
                throw new java.lang.InstantiationError("Not a property node");
        try {
            String propName=qnode.getAttribute(XMLParameters.paramName);
            String cimType=qnode.getAttribute(XMLParameters.paramTypeAssign);
            String classOrigin=qnode.getAttribute(XMLParameters.paramClassOrigin);
            String propagate=qnode.getAttribute(XMLParameters.paramPropagated);

            if (propName==null)
                throw new CIMException(CIMException.CIM_ERR_INVALID_PARAMETER,
                    "No property name");
            //
            // If no return data type, then property isn't properly defined
            //
            if (token==XMLNode.XML_ELEMENT_PROPERTY_REF) {
                String refClass=qnode.getAttribute(XMLParameters.paramReferenceClass);
                propertyDataType=new CIMDataType(refClass);
            } else  if (cimType!=null)
                propertyDataType=CIMDataType.getDataType(cimType);
            //
            // Array type property
            //
            if (token==XMLNode.XML_ELEMENT_PROPERTY_ARRAY) {
                String arraySize=qnode.getAttribute(XMLParameters.paramArraySize);
                if (arraySize!=null) {
                    propertyDataType=new CIMDataType(CIMDataType.findArrayType(
                            propertyDataType.getType()),new Integer(arraySize).intValue());
                } else {
                    propertyDataType=new CIMDataType(CIMDataType.findArrayType(propertyDataType.getType()));
                }
            }

            if (propertyDataType==null) {
                new CIMException(CIMException.CIM_ERR_FAILED,"Property was null datatype "+propName);
            }

            setName(propName);
            if (classOrigin!=null)
                setOriginClass(classOrigin);
            if (propagate!=null && propagate.equalsIgnoreCase("true"))
                setPropagated(true);
            else
                setPropagated(false);

            //
            // See if there are qualifiers
            //
            boolean embedded=false;
            for(qnode=qnode.getChild();qnode!=null && qnode.getToken()==XMLNode.XML_ELEMENT_QUALIFIER;
                    qnode=qnode.getNext()){
                CIMQualifier qual=new CIMQualifier(qnode);
                if (qual.getName().equalsIgnoreCase("embeddedobject")) {
                   embedded=true;
                }
                qualifiers.addElement(qual);
            }
            if (qnode!=null) {
               CIMValue propValue=new CIMValue(qnode);
               if (embedded) {
                  XMLParse embXml=null;
                  XMLNode embNode=null;
                  String xml=propValue.toString();
            //      System.err.println("embedded string: "+xml);
                  if (xml.startsWith("<![CDATA["))
                     xml=xml.substring(9,xml.length()-3);
                  embXml=new XMLParse(new StringReader(xml));
                  embNode=embXml.parse();
                  if (xml.startsWith("<I")) {
                     cimValue=new CIMValue(new CIMInstance(embNode),
                        CIMDataType.getPredefinedType(CIMDataType.OBJECT));
                  }
                  else if (xml.startsWith("<C")) {
                     cimValue=new CIMValue(new CIMClass(embNode),
                        CIMDataType.getPredefinedType(CIMDataType.CLASS));
                  }
               }
               else cimValue=CIMOMCast.castDataType(propValue,propertyDataType);
            }
        } catch (CIMException ce) {
            throw new java.lang.InstantiationError(ce.toString());
        }
    }


    /** 
	getQualifiers  returns the qualifiers for this property
	@retrun Vector list of qualifiers 
     */
    public Vector getQualifiers() {
        return(qualifiers);
    }

    /** 
	setQualifiers  sets the qualifiers for this property
	@param Vector list of qualifiers 
     */
    public void setQualifiers(Vector q){
        int i;
        int tsize=q.size();
        for(i=0;i<tsize;i++) {
            Object nq=q.elementAt(i);
            if (!(nq instanceof CIMQualifier))
                throw new Error(nq.toString()+" is not a qualifier");
        }
        qualifiers=q;
    }


    /** 
        getClassOrigin - Gets the classOrigin field from the property
        object. This will be a string with the name of the originating
        class for the property or empty if this is the originating class
        @return String with classOrigin name.
     */
    public String getOriginClass() {
        return(originClass);
    }

    /**
	setOriginClass sets the defining class for the property 
	@param String origin class name
     */
    public void setOriginClass(String ioriginClass) {
        originClass=ioriginClass;
    }


    /**
	setValue sets the value for the property 
	@param CIMvalue a CIM value
     */
    public void setValue(CIMValue aValue){
        try {
            //
            // If the property has been given a type then cast to that type
            // otherwise assume the type is the same as the passed value
            //
            if (propertyDataType!=null)
                cimValue=CIMOMCast.castDataType(aValue,propertyDataType);
            else {
                cimValue=aValue;
                if (propertyDataType==null)
                    propertyDataType=aValue.getType();
            }
        } catch (CIMException ce) {
            cimValue=null; // TODO, really would like to propagate the error
        }
    }


    /**
	getValue gets the value for the property 
	@return CIMvalue a CIM value
     */
    public CIMValue getValue() {
        return(cimValue);
    }

    /**
	setType sets the datatype for the property 
	@param CIMDataType type of the data
     */
    public void setType(CIMDataType aType){
        propertyDataType=aType;
    }

    /**
	getType sets the datatype for the property 
	@return CIMDataType type of the data
     */
    public CIMDataType getType(){
        return(propertyDataType);
    }

    /**
	getSize gets the datatype for the property 
	@return int size of the datatype
     */
    public int getSize(){
        return(sizeDataType);
    }

    /**
	setSize sets the datatype for the property 
	@param int size of the datatype
     */
    public void setSize(int isize) {
        sizeDataType=isize;
    }


    /**
	setOverridingProperty sets the for overriding property the property 
	@param String overriding property name
     */
    public void setOverridingProperty(String op){
        override=op;
    }

    /**
	getOverridingProperty gets the for overriding property the property 
	@return String overriding property name
     */
    public String getOverridingProperty() {
        return(override);
    }


    /**
        isReference returns TRUE if this property is a CIM reference
	@return boolean TRUE or FLASE
     */
    public boolean isReference(){
        return(propertyDataType.isReferenceType());
    }


    /**
        getQualifier returns the specified CIM qualifier
	@return CIMQualifier a cimqualifier for this property
     */
    public CIMQualifier getQualifier(String name) {
        int i;
        int tsize=qualifiers.size();
        for(i=0;i<tsize;i++) {
            CIMQualifier nq=((CIMQualifier)(qualifiers.elementAt(i)));
            if (nq.getName().equalsIgnoreCase(name)) {
                return((CIMQualifier)(qualifiers.elementAt(i)));
            }
        }
        return(null);
    }

    /**
        setQualifier set the specified CIM qualifier
	@param CIMQualifier a cimqualifier 
     */
    public CIMQualifier setQualifier(CIMQualifier cq) throws CIMException {
        int i;

        int tsize=qualifiers.size();
        for(i=0;i<tsize;i++) {
            CIMQualifier nq=((CIMQualifier)(qualifiers.elementAt(i)));
            if (nq.getName().equalsIgnoreCase(cq.getName())) {
                qualifiers.setElementAt(cq,i);
                return(cq);
            }
        }
        throw new CIMException(CIMException.CIM_ERR_NOT_FOUND,
            "Qualifier "+cq.getName()+" not found");
    }

    /**
        addQualifier adds the specified CIM qualifier to this property
	@param CIMQualifier a cimqualifier 
     */
    public void addQualifier(CIMQualifier cq) throws CIMException {
        int i;

        int tsize=qualifiers.size();
        for(i=0;i<tsize;i++) {
            CIMQualifier nq=((CIMQualifier)(qualifiers.elementAt(i)));
            if (nq.getName().equalsIgnoreCase(cq.getName())) {
                throw new CIMException(CIMException.CIM_ERR_ALREADY_EXISTS,
                        "Qualifier "+cq.getName()+" already exists");
            }
        }
        qualifiers.addElement(cq);
    }

    /**
        removeQualifier remove the specified CIM qualifier from this property
	@param String name of the Qualifier
     */
    public boolean removeQualifier(String name) {
        int i;

        int tsize=qualifiers.size();
        for(i=0;i<tsize;i++) {
            CIMQualifier nq=((CIMQualifier)(qualifiers.elementAt(i)));
            if (nq.getName().equalsIgnoreCase(name)) {
                qualifiers.removeElementAt(i);
                return(true);
            }
        }
        return(false);
    }

    /**
       toString returns a String representation of the CIMPropery.
       @return String empty or CIM Property string
    */
    public String toString() {

        if (cimValue!=null && cimValue.getValue()!=null)
            return(propertyDataType.toString()+":"+getName()+"="+cimValue.getValue());
        else
            return(propertyDataType.toString()+":"+getName()+"=null");
    }

   /**
       toMOF returns a MOF representation of the CIM Property.
       @return String MOF representation of this property.
    */
    public String toMOF() {
        String ret;
        int i;

        ret=new String();
        if (qualifiers!=null && qualifiers.size()>0) {
            ret=ret.concat("[");
            for(i=0;i<qualifiers.size();i++) {
                CIMQualifier nq=((CIMQualifier)(qualifiers.elementAt(i)));
                if (i>0)
                    ret=ret.concat(",");
                ret=ret.concat(nq.toMOF());
            }
            ret=ret.concat("]");
        }


        ret=ret.concat(propertyDataType.toMOF());
        ret=ret.concat(" ");
        ret=ret.concat(getName());
        if (cimValue!=null) {
            ret=ret.concat("=");
            ret=ret.concat(cimValue.toMOF());
        }
        ret=ret.concat(";\n");
        return(ret);
    }

    /**
        clone makes a copy of this property, including property name, identifier, 
        type, origin class, size, value, and qualifiers.
        @return Object cloned object
     */
    public Object clone(){
       return(clone(true,true));
    }

    /**
        clone makes a copy of this property, including property name, identifier, 
        type, origin class, size, value, and qualifiers. However you can control
        the flags includeQualifier and includeClassOrigin.

        @param boolean includeQualifier  TRUE/FALSE
        @param boolean includeClassOrigin TRUE/FALSE
        @return Object cloned object
     */
    public Object clone(boolean includeQualifier,
                                  boolean includeClassOrigin) {
        CIMProperty cp=new CIMProperty();

        //
        // Only things that can get changed on a per instance
        // basis get fully dup'd, the rest is just by reference
        //
        if (includeQualifier && qualifiers!=null) {
            cp.qualifiers=new Vector();
            int tsize=qualifiers.size();
            for(int i=0;i<tsize;i++) {
                CIMQualifier cq=(CIMQualifier)(qualifiers.elementAt(i));
                cp.qualifiers.addElement(cq.clone());
            }
        }
        cp.propertyDataType=propertyDataType;
        cp.sizeDataType=sizeDataType;

        cp.setName(getName());
        if (cp.override!=null)
            cp.override=override;
        if (cp.originClass!=null)
            cp.originClass=originClass;
        if (cimValue!=null)
            cp.cimValue=new CIMValue(cimValue.getValue(),cimValue.getType());

        cp.propagated=propagated;
        return(cp);
    }

   /** isKey - Tests the CIMProperty to determine if any
        qualifiers is a key indicating that this is a key
        property
        @return Returns true if this is a key property.
    */
    public boolean isKey() {
        int tsize=qualifiers.size();
        for(int j=0;j<tsize;j++) {
            CIMQualifier cq=(CIMQualifier)(qualifiers.elementAt(j));
            if (cq.getName().equalsIgnoreCase(CIMQualifier.CIM_QUAL_KEY))
                return(true);
        }
        return(false);
    }

   /**
       toXml returns a XML representation of the CIM Property.
       @return String XML representation of this property.
    */
    public String toXml() throws MalformedURLException {
        return(toXml(false,true,true));
    }

   /**
       toXml returns a XML representation of the CIM Property. You can
       also specify the flags.
       @param boolean localOnly flag
       @param boolean includeQualifiers flag
       @param boolean includeClassOrigin flag
       @return String XML  representation of this property.
    */
    public String toXml(boolean localOnly,
                        boolean includeQualifiers,
                        boolean includeClassOrigin) throws MalformedURLException{
        StringBuffer text=new StringBuffer();
        boolean isArray=false;
        boolean isRef=false;
        String cimName=getName();

        if (cimName==null)
            throw new MalformedURLException("property must have name");

        //
        // If only local definitions are required and this is a propagated
        // property then nothing to return
        //
        if (localOnly && propagated)
            return("");

        //
        if (propertyDataType!=null) {
            isArray=propertyDataType.isArrayType();
            isRef=propertyDataType.isReferenceType();

            if (isArray) {
                text=text.append("<PROPERTY.ARRAY NAME=\""+cimName+"\" "+
                                 "TYPE=\""+
                                CIMDataType.getPredefinedType(
                                            CIMDataType.findSimpleType(
                                            propertyDataType.getType())).toXml()+"\" ");
                int size=propertyDataType.getSize();
                if (size!=-1)   // i.e not unlimited
                    text=text.append("ARRAYSIZE=\""+size+"\" ");
            } else if (isRef) {
                text=text.append("<PROPERTY.REFERENCE NAME=\""+cimName+"\" "+
                                 "REFERENCECLASS=\""+propertyDataType.getRefClassName()+"\" ");
            } else
                text=text.append("<PROPERTY NAME=\""+cimName+"\" "+
                                 "TYPE=\""+propertyDataType.toXml()+"\" ");
        } else {
            throw new MalformedURLException("Property "+cimName+" has no type defined");
        }

        //
        // Bapu ( 08/07/02)
        // Should not include CLASSORIGIN if the  originClass is empty.
        //
        if (includeClassOrigin && originClass!=null && originClass.length() > 0)
            text=text.append("CLASSORIGIN=\""+originClass+"\" ");
        if (propagated==true)
            text=text.append("PROPAGATED=\"true\" ");

        text=text.append(">");

        if (qualifiers!=null && includeQualifiers) {
            for(int i=0;i<qualifiers.size();i++) {
                CIMQualifier cq=(CIMQualifier)(qualifiers.elementAt(i));
                text=text.append(cq.toXml());
            }
        }

        if (cimValue!=null) {
            if (cimValue.getType().getType()==CIMDataType.OBJECT) {
               Object o=cimValue.getValue();
               String xml=null;
               if (o instanceof CIMInstance) {
                  CIMInstance ei=(CIMInstance)cimValue.getValue();
                  xml="<![CDATA["+ei.toXml()+"]]>";
               //   System.err.println("--- xml: "+xml);
                  CIMValue nv=new CIMValue(xml);
                  text=text.append(nv.toXml());
               }
               else System.err.println("--- ??? 1");
            }
            else if (cimValue.getType().getType()==CIMDataType.CLASS) {
               Object o=cimValue.getValue();
               String xml=null;
               if (o instanceof CIMClass) {
                  CIMClass ec=(CIMClass)cimValue.getValue();
                  xml="<![CDATA["+ec.toXml(true)+"]]>";
               //   System.err.println("--- xml: "+xml);
                  CIMValue nv=new CIMValue(xml);
                  text=text.append(nv.toXml());
               }
               else System.err.println("--- ??? 2");
            }
            else text=text.append(cimValue.toXml());
        }

        if (isArray)
            text=text.append("</PROPERTY.ARRAY>");
        else if (isRef)
            text=text.append("</PROPERTY.REFERENCE>");
        else
            text=text.append("</PROPERTY>");

        return(text.toString());

    }

   /**
       Returns a filtered version of the property
       @param boolean localOnly flag
       @param boolean includeQualifiers flag
       @return CIMProperty a filters CIM Property.
    */
    public CIMProperty filter(boolean localOnly,
                    boolean includeQualifiers)  {
        //
        // If only local definitions are required and this is a propagated
        // property then nothing to return
        //
        if (localOnly && propagated)
            return(null);

        CIMProperty cp=new CIMProperty();
        cp.propertyDataType=propertyDataType;
        cp.sizeDataType=sizeDataType;
        cp.setName(getName());
        if (cp.override!=null)
            cp.override=override;
        if (cp.originClass!=null)
            cp.originClass=originClass;
        if (cimValue!=null)
            cp.cimValue=new CIMValue(cimValue.getValue(),cimValue.getType());
        cp.propagated=propagated;

        //
        if (qualifiers!=null && includeQualifiers) {
            cp.qualifiers=new Vector();
            for(int i=0;i<qualifiers.size();i++) {
                CIMQualifier cq=(CIMQualifier)(qualifiers.elementAt(i));
                cp.qualifiers.addElement(cq.clone());
            }
        }
        return(cp);
    }

   /** 
        setProgagated - Sets the propagated attribute true or fals.
        @param Boolean true or false representing propagated state to be set.
    */
    public void setPropagated(boolean value) {
        propagated=value;
    }

    /** 
	getPropagated - Tests if this property is propogated.
	@return - Returns true if the class is propogated.
     */
    public boolean getPropagated() {
        return(propagated);
    }

    //
    // Viktor Mihajlovski fixes : Date - 08/13/02
    // Fix for property comparison, the equals() function (from the parent)
    // doesn't work properly - a specific equals() function was added
    //
    /**
     * Compares identifier and value.
     * Creation date: (17/05/02 11:32:10 AM)
     * @return boolean TRUE if this equals the passed element
     * @param aProp java.lang.Object Another CIMProperty object
     */
    public boolean equals(Object anObj) 
    {
        if (anObj instanceof CIMProperty) 
        {
            CIMProperty prop = (CIMProperty) anObj;
            //Compare identifiers
            if (super.equals(prop)) 
            {
                //Compare values...
                if (getValue() != null) 
                {
                  return prop.getValue() != null && getValue().equals(prop.getValue());
                }
                return prop.getValue() == null;
            }
        }
        return false;
    }
}
