///////////////////////////////////////////////////////////////////////////////
//                                                                             
// JTOpen (IBM Toolbox for Java - OSS version)                              
//                                                                             
// Filename: PcmlData.java
//                                                                             
// The source code contained herein is licensed under the IBM Public License   
// Version 1.0, which has been approved by the Open Source Initiative.         
// Copyright (C) 1997-2003 International Business Machines Corporation and     
// others. All rights reserved.                                                
//                                                                             
///////////////////////////////////////////////////////////////////////////////

package com.ibm.as400.data;

import com.ibm.as400.access.AS400;
import com.ibm.as400.access.AS400Date;
import com.ibm.as400.access.AS400Time;
import com.ibm.as400.access.Trace;
import com.ibm.as400.access.ProgramParameter;                       // @B1A
import com.ibm.as400.access.BidiStringType;                         // @C9A

import java.io.IOException;                                         // @C1A
import java.io.ObjectOutputStream;                                  // @C1A
import java.io.OutputStream;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.TimeZone;

class PcmlData extends PcmlDocNode
{
    /***********************************************************
     Static Members
    ***********************************************************/

    // Constant values for type= attribute
    public static final int UNSUPPORTED = 0;
    public static final int CHAR   = 1;
    public static final int INT    = 2;
    public static final int PACKED = 3;
    public static final int ZONED  = 4;
    public static final int FLOAT  = 5;
    public static final int BYTE   = 6;
    public static final int STRUCT = 7;

    public static final int DATE        =  8; // PCML version 6.0
    public static final int TIME        =  9; // PCML version 6.0
    public static final int TIMESTAMP   = 10; // PCML version 6.0

    // Largest length= supported for type="char" and type="byte"
    public static final int MAX_STRING_LENGTH = 16*1024*1024-4112;          // @C6C @L17C Support max size of 16MB. 4112 is the header size for single level store

    // Serial verion unique identifier
    static final long serialVersionUID = 8578048664805881489L;

    // New attributes should be added to the end of this array
    private static final String DATAATTRIBUTES[] = {
        "name",
        "usage",
        "count",
        "minvrm",
        "maxvrm",
        "offset",
        "offsetfrom",
        "outputsize",
        "type",
        "length",
        "precision",
        "ccsid",
        "init",
        "struct",
        "passby",                       // PCML Ver. 2.0
        "bidistringtype",               // PCML Ver. 3.0               @C9A
        "trim",                         // PCML Ver. 4.0               @D1A
        "chartype",                     // PCML Ver. 4.0               @D2A
        // Note: "keyfield" is unique to RFML (Ver. 5.0), so is not listed here.
        "dateformat",                   // PCML Ver. 6.0
        "dateseparator",                // PCML Ver. 6.0
        "timeformat",                   // PCML Ver. 6.0
        "timeseparator"                 // PCML Ver. 6.0
    };
    private static final int VERSION_1_ATTRIBUTE_COUNT = 14;
    private static final int VERSION_2_ATTRIBUTE_COUNT = 15;
    private static final int VERSION_3_ATTRIBUTE_COUNT = 16;        // @C9A
    private static final int VERSION_4_ATTRIBUTE_COUNT = 18;        // @D1A @D2C
    private static final int VERSION_5_ATTRIBUTE_COUNT = 20;

    private static Hashtable bidiTypeMap_;

    /***********************************************************
     Instance Members
    ***********************************************************/

    // The "m_name" and "m_usage" attributes are implemented by PcmlDocNode

    // The following values are implemented by PcmlData and PcmlStruct
    private int    m_Count;         // count=, integer literal
    private String m_CountId;       // count=, element name
    private int    m_Offset;        // offset=, integer literal
    private String m_OffsetId;      // offset=, element name
    private boolean m_OffsetfromFixed;  // Flag indicating whether serialized version @A1A
                                        // of this object contains fix for offsetfrom
    private int    m_Offsetfrom;    // offsetfrom=, integer literal    @A1A
    private String m_OffsetfromId;  // offsetfrom=, element name
    private String m_Minvrm;        // minvrm=, string literal
    private int    m_MinvrmInt;     // minvrm=, from AS400.generateVRM()
    private String m_Maxvrm;        // maxvrm=, string literal
    private int    m_MaxvrmInt;     // maxvrm=, from AS400.generateVRM()
    private int    m_Outputsize;    // outputsize=, integer literal
    private String m_OutputsizeId;  // outputsize=, element name

    // The following attribute are implemented only by PcmlData
    private String m_TypeStr;       // type=, string literal
    private int    m_Type;          // type=, integer representing data type
    private int    m_Length;        // length=, integer literal
    private String m_LengthId;      // length=, element name
    private boolean m_LengthWasSpecified; // Indicates whether length was specified.  @D0A
    private int    m_Precision;     // precison=, integer literal
    private int    m_Ccsid;         // ccsid=, integer literal
    private String m_CcsidId;       // ccsid=, element name
    private boolean m_CcsidWasSpecified; // Indicates whether ccsid was specified.  @D0A
    private String m_Init;          // init=, string literal
    private String m_StructId;      // struct=, element name

    // The following attributes added for PCML v2.0
    private String m_PassbyStr;     // passby=, string literal                       @B1A
    private int    m_Passby;        // passby=, integer representing passby value    @B1A

    // The following attributes added for PCML v3.0
    private String m_BidistringtypeStr;     // bidistringtype=, string literal       @C9A
    private int    m_Bidistringtype;        // bidistringtype=, integer representing value    @C9A

    private boolean m_IsRfml;        // Indicates whether RFML versus PCML.  @D0A

    // The following attributes added for PCML v4.0
    private String m_TrimStr;        // trim=, string literal          @D1A
    private String m_CharType;       // chartype=, string literal      @D2A

    // The following attributes added for RFML v5.0 (not relevant to PCML)
    private String  m_KeyFieldStr;   // keyfield=, string literal
    private boolean m_KeyField;      // keyfield=, boolean representing value

    // The following attributes added for PCML v6.0
    private String m_DateFormat;    // dateformat=, string literal
    private String m_DateSeparator; // dateseparator=, string literal (single character)
    private String m_TimeFormat;    // timeformat=, string literal
    private String m_TimeSeparator; // timeseparator=, string literal (single character)

                                     // We store 'separator' values as String rather than char,
                                     // in order to use 'null' to indicate 'not set'.

    /***********************************************************
     Semi-Transient Members --
     Not written when serializing interface definition.
     Written when serializing ProgramCallDocument object.
    ***********************************************************/
    private PcmlDataValues m_scalarValue;                           // @C1C
    private PcmlDataVector m_vectorValue;                           // @C1C

    // Default constructor
    PcmlData()
    {
      this(false);                                                   // @D0A
    }

    // Constructor with description
    PcmlData(PcmlAttributeList attrs)                               // @C3C
    {
        this(attrs, false);                                         // @D0C
    }

    PcmlData(boolean isRfml)                                   // @D0A
    {
        m_IsRfml = isRfml;
    }

    // Constructor with description
    PcmlData(PcmlAttributeList attrs, boolean isRfml)          // @D0A
    {
        super(attrs);                                               // @C3C
        m_IsRfml = isRfml;                                          // @D0A
        setNodeType(PcmlNodeType.DATA);                             // @C3C

        // **********************************
        // Set attribute values
        //
        // The following code extracts the attribute values
        // from the parsed document node and
        // stores the values in private data members.
        // **********************************

        // Set count= attribute value
        setCount(getAttributeValue("count"));

        // Set offset= attribute value
        setOffset(getAttributeValue("offset"));

        // Set offsetfrom= attribute value
        setOffsetfrom(getAttributeValue("offsetfrom"));

        // Set minvrm= attribute value
        setMinvrm(getAttributeValue("minvrm"));

        // Set maxvrm= attribute value
        setMaxvrm(getAttributeValue("maxvrm"));

        // Set type= attribute value
        setType(getAttributeValue("type"));

        // Set length= attribute value
        setLength(getAttributeValue("length"));

        // Set precision= attribute value
        setPrecision(getAttributeValue("precision"));

        // Set ccsid= member variable
        setCcsid(getAttributeValue("ccsid"));

        // Set init= member variable
        setInit(getAttributeValue("init"));

        // Set outputsize= member variable
        setOutputsize(getAttributeValue("outputsize"));

        // Set struct= member variable
        setStruct(getAttributeValue("struct"));

        // Set passby= member variable
        setPassby(getAttributeValue("passby"));                     // @B1A

        // Set bidistringtype= member variable
        setBidiStringType(getAttributeValue("bidistringtype"));     // @C9A

        // Set trim= attribute value
        setTrim(getAttributeValue("trim"));                         // @D1A

        // Set chartype= attribute value
        setCharType(getAttributeValue("chartype"));                 // @D2A

        // Set keyfield= attribute value  (relevant only to RFML)
        setKeyField(getAttributeValue("keyfield"));

        // Set dateformat= attribute value
        setDateFormat(getAttributeValue("dateformat"));

        // Set dateseparator= attribute value
        setDateSeparator(getAttributeValue("dateseparator"));

        // Set timeformat= attribute value
        setTimeFormat(getAttributeValue("timeformat"));

        // Set timeseparator= attribute value
        setTimeSeparator(getAttributeValue("timeseparator"));

        m_scalarValue = null; // Transient data created as needed
        m_vectorValue = null; // Transient data created as needed

    }

    public Object clone()                                           // @C5A
    {                                                               // @C5A
        PcmlData node = (PcmlData) super.clone();                   // @C5A
        // Cloning does not include 'live' data, only the interface
        // definitions described by the PCML tags.
        // Null out the 'semi-transient' data values.
        node.m_scalarValue = null;                                  // @C5A
        node.m_vectorValue = null;                                  // @C5A

        return node;                                                // @C5A
    }                                                               // @C5A


    // Custom serialization
    private void writeObject(ObjectOutputStream out) throws IOException // @C1A
    {                                                               // @C1A
		synchronized (this)                                         // @C1A
		{                                                           // @C1A
			// Keep a local reference to the scalar and vector data values
	 		PcmlDataValues scalarValue = m_scalarValue;             // @C1A
			PcmlDataVector vectorValue = m_vectorValue;             // @C1A

			// If not saving with serialization, temporarily null out the
			// scalar and vector data values member variables
			// so they are not written to the ObjectOutputStream.
			if ( !getDoc().isSerializingWithData() )                // @C1A
			{                                                       // @C1A
				m_scalarValue = null;                               // @C1A
				m_vectorValue = null;                               // @C1A
			}                                                       // @C1A

			// Perform default serialization
			out.defaultWriteObject();                               // @C1A

			// Restore scalar and vector data values
			m_scalarValue = scalarValue;                            // @C1A
			m_vectorValue = vectorValue;                            // @C1A
		} // end of synchronized code                               // @C1A
    }                                                               // @C1A

	// Custom deserialization post-processing
	// This processing cannot be done during readObject() because
	// references to parent objects in the document are not yet set
	// due to the recursive nature of deserialization.
    void readObjectPostprocessing()                                 // @C1A
    {                                                               // @C1A
        if (m_scalarValue != null)                                  // @C1A
            m_scalarValue.readObjectPostprocessing();               // @C1A
        if (m_vectorValue != null)                                  // @C1A
            m_vectorValue.readObjectPostprocessing();               // @C1A

        super.readObjectPostprocessing();                           // @C1A
    }                                                               // @C1A

    // Returns a single PcmlDataValues given an array of indices
    private PcmlDataValues getPcmlDataValues(PcmlDimensions indices) throws PcmlException
    {
        int index;
        int[] myDimensions = getDimensions(indices).asArray();


        // Make sure enough indices are specified
        // Allow more indices than necessary in order to
        // allow nieces and nephews to get access to
        // values such as getCount()
        if (indices.size() < myDimensions.length)
        {
            throw new PcmlException(DAMRI.TOO_FEW_INDICES, new Object[] {new Integer(indices.size()), new Integer(myDimensions.length), getNameForException()} );
        }

        if (myDimensions.length == 0)
        {
            if (m_scalarValue == null)
            {
                m_scalarValue = new PcmlDataValues(this, new PcmlDimensions());
            }
            return m_scalarValue;
        }

        PcmlDataValues item;
        PcmlDataVector v, nextVector;
        PcmlDimensions myIndices = new PcmlDimensions();

        // If the vector for the first dimension (first index) has not been
        // created, create and initialize the vector
        if (m_vectorValue == null)
        {
            m_vectorValue = new PcmlDataVector(myDimensions[0], this, myIndices );
            for (int elem = 0; elem < myDimensions[0]; elem++)
            {
                m_vectorValue.addElement(null);
            }
        }

        // Walk down the tree of vectors. All but the last index is to a
        // vector; the last index is to a PcmlDataValues.
        long[] myDimensionTimestamps = getDimensionTimestamps(indices);
        v = m_vectorValue;
        for (int i = 0; i < myDimensions.length; i++)
        {

            // If the current dimension is more recent
            // than the dimension of the data, we need to
            // throw away all data at this and deeper dimensions
            // because it is stale.
            // In other words, the count= value was changed for this dimension
            // and we need to throw away all the values stored.
            if ( myDimensionTimestamps[i] > v.getTimestamp() )
            {
            	// Note:  For CPS discussion 8CDSG2 this code is causing a problem when the array is defined using 
            	//        a variable that is also returned. 
            	//    <program name="PCMLTST" path="/QSYS.LIB/PMR49262.LIB/PCMLTST.PGM">
                //       <data name="ARY" type="char" length="100" count="COUNT" usage="inputoutput" />
                //       <data name="COUNT" type="int" length="4" precision="32" usage="inputoutput" />
                //    </program>
            	// In this case, the dimension timestamp is really new, but v.getTimestamp() returns something low. 
            	// 
                v.redimension(myDimensions[i]);
            }

            // Get index for current dimension and add it to my working PcmlDimensions
            index = indices.at(i);
            myIndices.add(index);

            // Make sure index is not out of bounds
            if (index < 0 || index >= myDimensions[i])
            {
                throw new PcmlException(DAMRI.INDEX_OUT_OF_BOUNDS, new Object[] {new Integer(myDimensions[i]-1), new Integer(i), indices, getNameForException()} );  // @D0C Subtract 1 from myDimensions to get the upper end of range to come out right in the message.
            }

            // If we have are not on the last (deepest) dimension
            // get the PcmlDataVector for this dimension
            if (i != myDimensions.length - 1 )
            {
                nextVector = v.vectorAt(index);
                // If no PcmlDataVector has been created yet, create one now
                if (nextVector == null)
                {
                    nextVector = new PcmlDataVector(myDimensions[i+1], this, myIndices);
                    for (int elem = 0; elem < myDimensions[i+1]; elem++)
                    {
                        nextVector.addElement(null);
                    }
                    v.setElementAt(nextVector, index);
                }
                v = nextVector;
            }
            // We are on the last (deepest) dimension,
            // get the PcmlDataValues object -- at last!
            else
            {
                item = v.valuesAt(index);
                // If no PcmlDataValues has been created yet, create it now.
                if (item == null)
                {
                    item = new PcmlDataValues(this, myIndices);
                    v.setElementAt(item, index);
                }
                // Finally return the PcmlDataValues object requested
                return item;
            }
        }

        // We should never get here, but...
        throw new PcmlException(DAMRI.ERROR_ACCESSING_VALUE, new Object[] {indices, myDimensions, getNameForException()} );
    }

    // Get Timestamp of data
    long getTimestamp(PcmlDimensions indices) throws PcmlException
    {
        // Make sure enough indices are specified
        if ( indices.size() >= getNbrOfDimensions() )
        {
            PcmlDataValues values = getPcmlDataValues(indices);
            return values.getTimestamp();
        }
        else
        {
            throw new PcmlException(DAMRI.TOO_FEW_INDICES, new Object[] {new Integer(indices.size()), new Integer(getNbrOfDimensions()), getNameForException()} );
        }
    }

    // Get Java native value
    final Object getValue() throws PcmlException
    {
        return getValue(new PcmlDimensions());
    }

    // Get Java native value
    final Object getValue(PcmlDimensions indices) throws PcmlException
    {
        if (m_Type == CHAR)                                     // @C9A
        {
            return getStringValue(indices, m_Bidistringtype);   // @C9A
        }
        else                                                    // @C9A
        {
            // Make sure enough indices are specified
            if ( indices.size() >= getNbrOfDimensions() )
            {
                PcmlDataValues values = getPcmlDataValues(indices);
                return values.getValue();
            }
            else
            {
                throw new PcmlException(DAMRI.TOO_FEW_INDICES, new Object[] {new Integer(indices.size()), new Integer(getNbrOfDimensions()), getNameForException()} );
            }
        }
    }


    // Get String value specifying string type
    final String getStringValue(PcmlDimensions indices, int type)
        throws PcmlException                                            // @C9A
    {
        Object val = null;

        // Make sure enough indices are specified
        if ( indices.size() >= getNbrOfDimensions() )                   // @C9A
        {
            PcmlDataValues values = getPcmlDataValues(indices);         // @C9A
            if (m_Type == CHAR)                                         // @CBA
                values.setStringType(type); // Set the string type             @C9A
            val = values.getValue();    // Get the value              @C9A @CAC
            if (val == null)                                            // @CBA
            {
                return null;                                            // @CBA
            }
            if (val instanceof Number)
            {
                return ((Number)val).toString();                        // @C9A
            }
            else if (val instanceof String)                             // @C9A
            {
                return (String)val;                                     // @C9A
            }
            else                                                        // @C9A
            {
                throw new PcmlException(DAMRI.STRING_OR_NUMBER, new Object[] {val.getClass().getName(), getNameForException()} );   // @C9A
            }
        }
        else
        {
            throw new PcmlException(DAMRI.TOO_FEW_INDICES, new Object[] {new Integer(indices.size()), new Integer(getNbrOfDimensions()), getNameForException()} );  // @C9A
        }
    }

    // Set Java native value
    final void setValue(Object v) throws PcmlException
    {
        setValue(v, new PcmlDimensions());
    }

    // Set Java native value
    final void setValue(Object v, PcmlDimensions indices) throws PcmlException
    {
        // Make sure enough indices are specified
        if ( indices.size() >= getNbrOfDimensions() )
        {
            PcmlDataValues values = getPcmlDataValues(indices);
            values.setStringType(m_Bidistringtype);
            values.setValue(v);
        }
        else
        {
            throw new PcmlException(DAMRI.TOO_FEW_INDICES, new Object[] {new Integer(indices.size()), new Integer(getNbrOfDimensions()), getNameForException()} );
        }
    }

    // Set String value specifying string type
    final void setStringValue(String val, PcmlDimensions indices, int type)
        throws PcmlException                                            // @C9A
    {
        // Make sure enough indices are specified
        if ( indices.size() >= getNbrOfDimensions() )                   // @C9A
        {
            PcmlDataValues values = getPcmlDataValues(indices);         // @C9A
            values.flushValues();       // Flush current values            @C9A
            values.setStringType(type); // Set the string type             @C9A
            values.setValue(val);       // Set the value                   @C9A
        }
        else
        {
            throw new PcmlException(DAMRI.TOO_FEW_INDICES, new Object[] {new Integer(indices.size()), new Integer(getNbrOfDimensions()), getNameForException()} );  // @C9A
        }
    }

    // Set IBM i system bytes
    void setBytes(byte[] ba)
    {
    }

   /**
    * Return the list of valid attributes for the data element.
    **/
    String[] getAttributeList()                                 // @C7A
    {
        int returnCount = 0;                                    // @C7A
        String returnArray[];                                   // @C7A

        if ( getDoc().getVersion().compareTo("2.0") < 0 )       // @C7A
            returnCount = VERSION_1_ATTRIBUTE_COUNT;            // @C7A
        else if ( getDoc().getVersion().compareTo("3.0") < 0 )  // @C9A
            returnCount = VERSION_2_ATTRIBUTE_COUNT;            // @C9A
        else if ( getDoc().getVersion().compareTo("4.0") < 0 )  // @D1A
            returnCount = VERSION_3_ATTRIBUTE_COUNT;            // @D1A
        else if ( getDoc().getVersion().compareTo("6.0") < 0 )
            returnCount = VERSION_4_ATTRIBUTE_COUNT;
        else                            // Anything else: Return the entire array
            return DATAATTRIBUTES;                              // @C7A

        returnArray = new String[returnCount];                  // @C7A

        System.arraycopy(DATAATTRIBUTES, 0, returnArray, 0, returnCount);   // @C7A
        return returnArray;                                     // @C7A
    }


    final int getCcsid(PcmlDimensions indices) throws PcmlException
    {
        int tmpCcsid = resolveIntegerValue( getCcsid(),
                                            getCcsidId(),
                                            indices );
        // If a CCSID is not explicitly defined for this element,
        // use the CCSID from the <program> element
        if (tmpCcsid == 0)
        {                                                           // @C2A
            PcmlNode node = getParent();                            // @C2A
            while (node.getParent() != getDoc())                    // @C2A
                node = node.getParent();                            // @C2A
            // If this element is a descendent of a Program element
            // use the CCSID saved the last time the
            if (node instanceof PcmlProgram)                        // @C2A
                return ((PcmlProgram) node).getProgramCCSID();      // @C2A
            else                                                    // @C2A
                return getDoc().getAs400().getCcsid();              // @C2A
        }                                                           // @C2A
        else
            return tmpCcsid;
    }

    // Get the run-time dimension for this element
    final int getCount(PcmlDimensions indices) throws PcmlException
    {
        return resolveIntegerValue( getCount(),
                                    getCountId(),
                                    indices );
    }

    // @E0A -- New XPCML method
    // Get the run-time dimension for this element but don't throw an exception if count not set
    final int getXPCMLCount(PcmlDimensions indices) throws PcmlException
    {
        int rc;
        try {
          rc = resolveIntegerValue( getCount(),
                                    getCountId(),
                                    indices );
          return rc;
        }
        catch (Exception e)
        {
           return 0;
        }
    }

    // Get the count= integer literal value, if any
    public final int getCount()
    {
        return m_Count;
    }

    // Get the count= resolved element name, if any
    public final String getCountId()
    {
        return resolveRelativeName(m_CountId);
    }

    // Get the count= unresolved element name, if any
    public final String getUnqualifiedCountId()                     // @C7A
    {
        return m_CountId;                                           // @C7A
    }

    final int getLength(PcmlDimensions indices) throws PcmlException
    {
        return resolveIntegerValue( getLength(),
                                    getLengthId(),
                                    indices );
    }

    // Get the run-time offset value for this element
    final int getOffset(PcmlDimensions indices) throws PcmlException
    {
        return resolveIntegerValue( getOffset(),
                                    getOffsetId(),
                                    indices );
    }

    // Get the offset= integer literal value, if any
    public final int getOffset()
    {
        return m_Offset;
    }

    // Get the offset= resolved element name, if any
    public final String getOffsetId()
    {
        return resolveRelativeName(m_OffsetId);
    }

    // Get the offset= unresolved element name, if any
    public final String getUnqualifiedOffsetId()                    // @C7A
    {
        return m_OffsetId;                                          // @C7A
    }

    // Return indication of whether this object contains
    // the fix for ofsetfrom
    private final boolean isOffsetfromFixed()                       // @A1A
    {                                                               // @A1A
        return m_OffsetfromFixed;                                   // @A1A
    }                                                               // @A1A

    // Get the passby= value as an integer (ProgramParameter constant)
    public final int getPassby()                                    // @B1A
    {                                                               // @B1A
        if (m_PassbyStr != null)                                    // @B1A
            return m_Passby;                                        // @B1A
        else                                                        // @B1A
            return ProgramParameter.PASS_BY_REFERENCE;              // @B1A
    }                                                               // @B1A

    // Get the bidistringtype= value as an integer
    public final String getBidistringtypeStr()                    // @C9A @CAC
    {
        return m_BidistringtypeStr;                            // @C9A @CAC
    }

    // Get the offsetfrom= integer literal value, if any
    final int getOffsetfrom()                                       // @A1C
    {
        if ( isOffsetfromFixed() )                                  // @A1A
            return m_Offsetfrom;                                    // @A1C
        else                                                        // @A1A
            return -1;                                              // @A1A
    }

    // Get the offsetfrom= resolved element name, if any
    final String getOffsetfromId()                                  // @A1A
    {                                                               // @A1A
        return resolveRelativeName(m_OffsetfromId);                 // @A1A
    }                                                               // @A1A

    // Get the offset= unresolved element name, if any
    public final String getUnqualifiedOffsetfromId()                // @C7A
    {
        return m_OffsetfromId;                                      // @C7A
    }

    // Returns an array of integers containing the array dimensions
    // Notes:
    //      getDimensions().length == 0 for scalar data
    PcmlDimensions getDimensions(PcmlDimensions indices) throws PcmlException
    {
        PcmlDimensions myDimensions = null;
        PcmlNode node = getParent();                                // @CCA

        // Retrieve array dimensions from all ancestors
        if (node instanceof PcmlData)                               // @CCC
        {
            myDimensions = ((PcmlData) node).getDimensions(indices);// @CCC
        }
        else
        if  (node instanceof PcmlStruct)                            // @CCC
        {
            myDimensions = ((PcmlStruct) node).getDimensions(indices);  // @CCC
        }
        else
        {
            myDimensions = new PcmlDimensions(getNbrOfDimensions());
        }

        // If this node is defined as an array, add its dimension
        if (isArray())
        {
            int myCount = getCount(indices);
            myDimensions.add(myCount);
        }

        return myDimensions;
    }

    // Returns an array of integers containing the timestamps
    // for each of the array dimensions for this node.
    // Notes:
    //      getNbrOfDimensions() == 0 for scalar data
    //      getNbrOfDimensions() == getDimensions().length
    long[] getDimensionTimestamps(PcmlDimensions indices) throws PcmlException
    {
        long[] myTimestamps;
        Integer myIndex = null;
        long[] previousTimestamps;
        PcmlNode node = getParent();                                                // @CCA

        // If an array is defined at this node,
        // remove its dimension from the array of indices
        if (isArray())
        {
            myIndex = indices.integerAt(indices.size()-1);
            indices.remove();
        }

        // Retrieve array dimensions from all ancestors
        if (node instanceof PcmlData)                                               // @CCC
        {
            previousTimestamps = ((PcmlData) node).getDimensionTimestamps(indices); // @CCC
        }
        else
        if  (node instanceof PcmlStruct)                                            // @CCC
        {
            previousTimestamps = ((PcmlStruct) node).getDimensionTimestamps(indices);   // @CCC
        }
        else
        {
            previousTimestamps = new long[0];
        }

        // If this node is defined as an array, add its dimension
        // back to the array of indices and get the time stamp for this dimension.
        if (myIndex != null)
        {
            int i;
            indices.add(myIndex);
            myTimestamps = new long[previousTimestamps.length + 1];
            for (i = 0; i < previousTimestamps.length; i++)
            {
                myTimestamps[i] = previousTimestamps[i];
            }
            myTimestamps[i] = resolveDimensionTimestamp(indices);
            if (i > 0)
            {
                myTimestamps[i] = Math.max(myTimestamps[i], myTimestamps[i-1]);
            }
        }
        else
        {
            myTimestamps = previousTimestamps;
        }

        return myTimestamps;
    }

    // Returns the number of dimensions for this data node
    // Notes:
    //      getNbrOfDimensions() == 0 for scalar data
    //      getNbrOfDimensions() == getDimensions().length
    int getNbrOfDimensions()
    {
        int total = 0;
        PcmlNode node = getParent();                                // @CCA

        if (isArray())
            total++;

        if (node instanceof PcmlData)                               // @CCC
            total += ((PcmlData)node).getNbrOfDimensions();         // @CCC
        else
            if (node instanceof PcmlStruct)                         // @CCC
                total += ((PcmlStruct)node).getNbrOfDimensions();   // @CCC

        return total;
    }

    int getOutputsize(PcmlDimensions indices) throws PcmlException
    {
        int totalSize = 0;
        int myCount;
        boolean processArray;

        // If outputsize= was specified for this element use that
        // as the output size for this and all descendents.
        totalSize = resolveIntegerValue( getOutputsize(),
                                         getOutputsizeId(),
                                         indices );
        if (totalSize > 0)
            return totalSize;

        if (isArray() && indices.size() < getNbrOfDimensions() )
        {
            myCount = getCount(indices);
            processArray = true;
        }
        else
        {
            myCount = 1;
            processArray = false;
        }

        for (int myIndex  = 0; myIndex < myCount; myIndex++)
        {

            if (processArray)
            {
                indices.add(myIndex);
            }


            switch (getDataType())
            {

                case PcmlData.STRUCT:
                    Enumeration children;
                    PcmlDocNode child;

                    children = getChildren();
                    while (children.hasMoreElements())
                    {
                        child = (PcmlDocNode) children.nextElement();
                        switch (child.getNodeType())
                        {
                            case PcmlNodeType.STRUCT:
                                totalSize += ((PcmlStruct) child).getOutputsize(indices);
                                break;
                            case PcmlNodeType.DATA:
                                totalSize += ((PcmlData) child).getOutputsize(indices);
                                break;
                            default:
                                throw new PcmlException(DAMRI.BAD_NODE_TYPE, new Object[] {new Integer(child.getNodeType()) , getNameForException()} );
                        }
                    }
                    break;

                // For all scalar types
                default:
                    totalSize += getPcmlDataValues(indices).byteLength();
                    if (totalSize == 0)
                    {
                        totalSize = 32;
                    }

            }

            if (processArray)
            {
                indices.remove();
            }

        } // END: for myIndex

        return totalSize;
    }

    // Get the trim= resolved element name, if any
    public final String getTrim()                                   // @D1A
    {                                                               // @D1A
        return m_TrimStr;                                           // @D1A
    }                                                               // @D1A

    // Get the trim= resolved element name, if any
    public final String getCharType()                               // @D2A
    {                                                               // @D2A
        return m_CharType;                                          // @D2A
    }                                                               // @D2A

    boolean isArray()
    {
        if ( getCount() > 0 )
            return true;
        else
            if ( getCountId() != null )
                return true;

        return false;
    }

    // Returns true if this node is defined as an array or
    // has an ancestor defined as an array.
    boolean isInArray()
    {
        PcmlNode node = getParent();                                // @CCA

        if (isArray())
            return true;
        else
            if (node instanceof PcmlData)                           // @CCC
                return ((PcmlData)node).isInArray();                // @CCC
            else
                if (node instanceof PcmlStruct)                     // @CCC
                    return ((PcmlStruct)node).isInArray();          // @CCC
                else
                    return false;
    }

    // Returns true if the length attribute has been specified.        @D0A
    public final boolean isLengthSpecified()
    {
      return (m_LengthWasSpecified || m_Length != 0 || m_LengthId != null);
      // Note: This conditional is beefed-up to handle the case where a PcmlData object from an older version (before m_LengthWasSpecified was added) was serialized and then deserialized into the current version.
    }

    // Returns true if this document element is supported at the
    // at the VRM of the current host.
    // Returns false if not.
    boolean isSupportedAtHostVRM() throws PcmlException             // @A1A
    {                                                               // @A1A
        if (m_IsRfml) return true;                                  // @D0A
        int hostVrm = getAs400VRM();      // VRM of the IBM i system  @A1A

        // If the minvrm= for this element is greater than the server VRM
        // do not process this element. The item is not available at this release.
        if (getMinvrm() > hostVrm)                                  // @A1A
        {                                                           // @A1A
            return false;                                           // @A1A
        }                                                           // @A1A

        // If the maxvrm= for this element is less than the server VRM
        // do not process this element. The item is not available at this release.
        if (getMaxvrm() < hostVrm)                                  // @A1A
        {                                                           // @A1A
            return false;                                           // @A1A
        }                                                           // @A1A

        return true;                                                // @A1A
    }                                                               // @A1A


    // Convert Java object IBM i system bytes
    // Returns the number of bytes converted
    int toBytes(OutputStream bytes, int offset, PcmlDimensions indices) throws PcmlException
    {
        int totalBytes = 0;
        int myCount;
        boolean processArray;

        // Do not process if this element is not supported at the
        // VRM of the current host.
        if ( !isSupportedAtHostVRM() )                              // @A1C
            return 0;

        // If this is an array element, set up array processing information
        if (isArray() && indices.size() < getNbrOfDimensions() )
        {
            myCount = getCount(indices);
            processArray = true;
        }
        else // Non-array element, only process once.
             // Note: Although this element is not an array
             // (i.e. does not have a count= attribute)
             // It may be a child of an element that is an array.
        {
            myCount = 1;
            processArray = false;
        }

        // -----------------------------------------------------------
        // Now actually convert data to bytes
        // -----------------------------------------------------------
        for (int myIndex  = 0; myIndex < myCount; myIndex++)
        {

            if (processArray)
            {
                indices.add(myIndex);
            }

            switch (getDataType())
            {

                case PcmlData.STRUCT:
                    Enumeration children;
                    PcmlDocNode child;

                    children = getChildren();
                    while (children.hasMoreElements())
                    {
                        child = (PcmlDocNode) children.nextElement();
                        switch (child.getNodeType())
                        {
                            case PcmlNodeType.STRUCT:
                                totalBytes += ((PcmlStruct) child).toBytes(bytes, offset + totalBytes, indices);
                                break;
                            case PcmlNodeType.DATA:
                                totalBytes += ((PcmlData) child).toBytes(bytes, offset + totalBytes, indices);
                                break;
                            default:
                                throw new PcmlException(DAMRI.BAD_NODE_TYPE, new Object[] {new Integer(child.getNodeType()) , getNameForException()} );
                        } // END: switch (child.getNodeType())
                    } // END: while (children.hasMoreElements())
                    break;

                default:
                    // Convert scalar leaf node based on current dimensions
                    totalBytes += getPcmlDataValues(indices).toBytes(bytes, offset + totalBytes);
                    break;

            } // END: switch (getDataType())

            if (processArray)
            {
                indices.remove();
            }

        } // END: for myIndex

        return totalBytes;
    } // public void toBytes(OutputStream bytes, int offset, PcmlDimensions indices)


    // Parses array of bytes and stores for later conversion
    // to Java objects. This allows for lazy data translation
    // for better performance.
    // Returns the number of bytes consumed from the input byte array
    // Note: This may be larger than the number of bytes saved for this element
    //       because of bytes skipped due to an offset value.
    int parseBytes(byte[] bytes, int offset, Hashtable offsetStack, PcmlDimensions indices) throws PcmlException
    {
        PcmlData dataNode;          // Child of this element that is a <data> node
        PcmlStruct structNode;      // Child of this element that is a <struct> node
        int nbrBytes = 0;
        int myCount;
        boolean processArray;

        // Do not process if this element is not supported at the
        // VRM of the current host.
        if ( !isSupportedAtHostVRM() )                              // @A1C
            return 0;

        // If this is an array element, set up array processing information
        if (isArray() && indices.size() < getNbrOfDimensions() )
        {
            myCount = getCount(indices);
            processArray = true;
        }
        else // Non-array element, only process once.
             // Note: Although this element is not an array
             // (i.e. does not have a count= attribute)
             // It may be a child of an element that is an array.
        {
            myCount = 1;
            processArray = false;
        }

        // -----------------------------------------------------------
        // Calculate bytes to skip based on the offset=
        // and offsetfrom= attributes.
        // -----------------------------------------------------------
        int skipBytes = 0;               // Initially, no need to skip bytes            @C8A
        if (getDataType() == PcmlData.STRUCT)                                       //  @C8A
        {
            int myOffset = getOffset(indices);  // Retrieve offset value for this element   @C8A
            if (myOffset > 0)                // If this element has a non-zero offset       @C8A
            {
                // Determine from where the offset is based
                Integer myOffsetbase = null;                                            //  @C8A
                String myOffsetfromId = getOffsetfromId();      // Get offsetfrom= element name, if any    @C8A

                // If offsetfrom= was specified with the name of another element,
                // get the base for the offset from the offset stack.
                // The offset stack is a stack of beginning offsets for all
                // ancestors of the current element. The offsetfrom= value must be one
                // of these ancestors or an error will be reported.
                if (myOffsetfromId != null)                             // @C8A
                {
                    myOffsetbase = (Integer) offsetStack.get(myOffsetfromId); // @C8A
                    if (myOffsetbase == null)                           // @C8A
                    {
                        throw new PcmlException(DAMRI.OFFSETFROM_NOT_FOUND, new Object[] {myOffsetfromId, getNameForException()} ); // @C8A
                    }
                }
                else
                {
                    // If offsetfrom= was specified with an integer literal, use it.
                    if (getOffsetfrom() >= 0)                           // @C8A
                    {
                        myOffsetbase = new Integer(getOffsetfrom());    // @C8A
                    }
                    // getOffsetfrom() returns -1 to indicate that offset from was not specified.
                    // No offsetfrom= was specified, the offset will be relative to the
                    // beginning offset of the parent of this elements parent.
                    // This is the first (most recent) entry in the offset stack.
                    else
                    {
                        myOffsetbase = (Integer) offsetStack.get( ((PcmlDocNode) getParent()).getQualifiedName());  //@C8A
                    }
                }

                // Add the base value to the offset value
                if (myOffsetbase != null)                               // @C8A
                {
                    myOffset = myOffset + myOffsetbase.intValue();      // @C8A
                }

                // If the total offset value is greater than the current
                // offset into the input byte array, calculate the
                // number of bytes to skip.
                // (Bytes skipped over as a result ofthe offset=.)
                if (myOffset > offset)                                  // @C8A
                {
                    skipBytes = myOffset - offset;                      // @C8A
                }
            } // End calculating bytes to skip because of offset= attribute
        }

        // -----------------------------------------------------------
        // Now actually parse the bytes for this element
        // -----------------------------------------------------------
        for (int myIndex  = 0; myIndex < myCount; myIndex++)
        {

            if (processArray)
            {
                indices.add(myIndex);
            }

            // If this is not a structure, get the PcmlDataValues object and
            // parse the bytes
            if (getDataType() != PcmlData.STRUCT)
            {
                nbrBytes += getPcmlDataValues(indices).parseBytes(bytes, offset + skipBytes + nbrBytes, offsetStack);   // @C8C
            }
            else
            {
                Enumeration children;
                PcmlDocNode child;

                // Add this node to the offset stack
                String qName = getQualifiedName();
                if (!qName.equals(""))
                {
                    offsetStack.put(qName, new Integer(offset + skipBytes + nbrBytes)); // @C8C
                }

                children = getChildren();
                while (children.hasMoreElements())
                {
                    child = (PcmlDocNode) children.nextElement();
                    switch (child.getNodeType())
                    {
                        case PcmlNodeType.STRUCT:
                            structNode = (PcmlStruct) child;
                            nbrBytes += structNode.parseBytes(bytes, offset + skipBytes + nbrBytes, offsetStack, indices);  // @C8C
                            break;
                        case PcmlNodeType.DATA:
                            dataNode = (PcmlData) child;
                            nbrBytes += dataNode.parseBytes(bytes, offset + skipBytes + nbrBytes, offsetStack, indices);    // @C8C
                            break;
                        default:
                            throw new PcmlException(DAMRI.BAD_NODE_TYPE, new Object[] {new Integer(child.getNodeType()) , getNameForException()} );
                    } // END: switch (child.getNodeType())
                } // END: while (children.hasMoreElements())

                // Remove this node from the offset stack
                if (!qName.equals(""))
                {
                    offsetStack.remove(qName);
                }

            }

            if (processArray)
            {
                indices.remove();
            }

        } // END: for myIndex

        return nbrBytes + skipBytes;                // @C8C
    } // public int parseBytes(byte[] bytes, int offset)


    // Resolve an integer value from either a named element or a literal
    private int resolveIntegerValue(int intLiteral, String name, PcmlDimensions indices) throws PcmlException
    {
        PcmlNode node;
        PcmlData dataNode;
        Object nodeValue;

        if (name != null)
        {
            node = getDoc().getElement(name);
            if (node instanceof PcmlData)
            {
                dataNode = (PcmlData) node;
                nodeValue = dataNode.getValue(indices);
                if (nodeValue instanceof String)
                {
                    return Integer.parseInt((String) nodeValue);
                }
                else if (nodeValue instanceof Number)
                {
                    return ((Number) nodeValue).intValue();
                }
                else
                {
                    if (nodeValue == null)
                        throw new PcmlException(DAMRI.INPUT_VALUE_NOT_SET, new Object[] {dataNode.getNameForException()} );
                    else
                        throw new PcmlException(DAMRI.STRING_OR_NUMBER, new Object[] {nodeValue.getClass().getName(), dataNode.getNameForException()} );
                }
            }
            else
            {
                if (node == null)
                    throw new PcmlException(DAMRI.ELEMENT_NOT_FOUND, new Object[] {name, "<data>"} );
                else
                    throw new PcmlException(DAMRI.WRONG_ELEMENT_TYPE, new Object[] {name, "<data>"} );
            }
        }
        return intLiteral;
    }

    // Resolve a timestamp for the given indices
    private long resolveDimensionTimestamp(PcmlDimensions indices) throws PcmlException
    {
        PcmlNode node;
        String name =  getCountId();

        if (name != null)
        {
            node = getDoc().getElement(name);
            if (node instanceof PcmlData)
            {
                return ((PcmlData)node).getTimestamp(indices);
            }
            else
            {
                if (node == null)
                    throw new PcmlException(DAMRI.ELEMENT_NOT_FOUND, new Object[] {name, "<data>"} );
                else
                    throw new PcmlException(DAMRI.WRONG_ELEMENT_TYPE, new Object[] {name, "<data>"} );
            }
        }
        return Long.MIN_VALUE;
    }

    // Get the ccsid= integer literal value, if any
    public final int getCcsid()
    {
        return m_Ccsid;
    }

    // Get the ccsid= resolved element name, if any
    public final String getCcsidId()
    {
        return resolveRelativeName(m_CcsidId);
    }

    // Get the offset= unresolved element name, if any
    public final String getUnqualifiedCcsidId()                     // @C7A
    {
        return m_CcsidId;                                           // @C7A
    }

    // Get the type= value as an Integer
    public final int getDataType()
    {
        return m_Type;
    }

    // Get the type= value as a String
    public final String getDataTypeString()
    {
        return m_TypeStr;
    }

    // Get the init= string value, if any
    public final String getInit()
    {
        return m_Init;
    }

    // Get the length= integer literal value, if any
    public final int getLength()
    {
        return m_Length;
    }

    // Get the length= resolved element name, if any
    public final String getLengthId()
    {
        return resolveRelativeName(m_LengthId);
    }

    // Get the offset= unresolved element name, if any
    public final String getUnqualifiedLengthId()                    // @C7A
    {
        return m_LengthId;                                          // @C7A
    }

    // Get the maxvrm= integer value -- compatible w/ AS400.generateVRM()
    // Returns Integer.MAX_VALUE is maxvrm= was not specified
    public final int getMaxvrm()
    {
        return m_MaxvrmInt;
    }

    // Get the maxvrm= String value
    public final String getMaxvrmString()                       // @C7A
    {
        return m_Maxvrm;
    }

    // Get the minvrm= integer value  -- compatible w/ AS400.generateVRM()
    // Returns Integer.MIN_VALUE minvrm= was not specified
    public final int getMinvrm()
    {
        return m_MinvrmInt;
    }

    // Get the minvrm= String value
    public final String getMinvrmString()                       // @C7A
    {
        return m_Minvrm;
    }

    // Get the outputsize= integer literal value, if any
    public final int getOutputsize()
    {
        return m_Outputsize;
    }

    // Get the outputsize= resolved element name, if any
    public final String getOutputsizeId()
    {
        return resolveRelativeName(m_OutputsizeId);
    }

    // Get the offset= unresolved element name, if any
    public final String getUnqualifiedOutputsizeId()                // @C7A
    {
        return m_OutputsizeId;                                      // @C7A
    }

    // Get the precision= integer literal value, if any
    public final int getPrecision()
    {
        return m_Precision;
    }

    // Get the struct= element name, if any
    public final String getStruct()
    {
        return m_StructId;
    }

    // Get the dateformat= value, if any
    public final String getDateFormat()
    {
        return m_DateFormat;
    }

    // Get the dateseparator= value, if any
    public final String getDateSeparator()
    {
        return m_DateSeparator;
    }

    // Get the timeformat= value, if any
    public final String getTimeFormat()
    {
        return m_TimeFormat;
    }

    // Get the timeseparator= value, if any
    public final String getTimeSeparator()
    {
        return m_TimeSeparator;
    }

    // Get the keyfield= value, if any
    public boolean isKeyField()
    {
        return m_KeyField;
    }

    // Set the count= attribute
    private void setCount(String count)
    {
        // Handle null or empty string
        if (count == null || count.equals(""))
        {
            m_Count = 0;
            m_CountId = null;
            return;
        }

        // Try to parse an integer from the attribute value
        try
        {
            m_Count = Integer.parseInt(count);
            m_CountId = null;
        }
        // If value is not an integer, it must be an element name
        // checkAttributes() will be called later to verify the element name
        catch (NumberFormatException e)
        {
            m_Count = 0;
            m_CountId = count;
        }
    }

    private void setCcsid(String ccsid)
    {
        // Handle null or empty string
        if (ccsid == null || ccsid.equals(""))
        {
            m_Ccsid = 0;
            m_CcsidId = null;
            return;
        }

        m_CcsidWasSpecified = true;                           // @D0A

        // Try to parse an integer from the attribute value
        try
        {
            m_Ccsid = Integer.parseInt(ccsid);
            m_CcsidId = null;
        }
        // If value is not an integer, it must be an element name
        // checkAttributes() will be called later to verify the element name
        catch (NumberFormatException e)
        {
            m_Ccsid = 0;
            m_CcsidId = ccsid;
        }
    }

    void setInit(String init)      // E0C
    {
// @D0D
//        // Handle null or empty string
//        if (init == null || init.equals(""))
//        {
//            m_Init = null;
//            return;
//        }

        // Save the attribute value
        m_Init = init;
        // checkAttributes() will verify the value against the data type
    }

    protected void setLength(String length)           // @D0C
    {
        // Handle null or empty string
        if (length == null || length.equals(""))
        {
            m_Length = 0;
            m_LengthId = null;
            return;
        }

        m_LengthWasSpecified = true;  // @D0A

        // Try to parse an integer from the attribute value
        try
        {
            m_Length = Integer.parseInt(length);
            m_LengthId = null;
        }
        // If value is not an integer, it must be an element name
        // checkAttributes() will be called later to verify the element name
        catch (NumberFormatException e)
        {
            m_Length = 0;
            m_LengthId = length;
            // checkAttributes() will make sure m_LengthId resolves to a <data> element with type="int"
        }
    }

    private void setMaxvrm(String maxvrm)
    {
        m_MaxvrmInt = Integer.MAX_VALUE;
        // Handle null or empty string
        if (maxvrm == null || maxvrm.equals(""))
        {
            m_Maxvrm = null;
            return;
        }

        // Save the attribute value
        m_Maxvrm = maxvrm;
    }

    private void setMinvrm(String minvrm)
    {
        m_MinvrmInt = Integer.MIN_VALUE;
        // Handle null or empty string
        if (minvrm == null || minvrm.equals(""))
        {
            m_Minvrm = null;
            return;
        }

        m_Minvrm = minvrm;
    }

    private void setOffset(String offset)
    {
        // Handle null or empty string
        if (offset == null || offset.equals(""))
        {
            m_Offset = 0;
            m_OffsetId = null;
            return;
        }

        // Try to parse an integer from the attribute value
        try
        {
            m_Offset = Integer.parseInt(offset);
            m_OffsetId = null;
        }
        // If value is not an integer, it must be an element name
        // checkAttributes() will be called later to verify the element name
        catch (NumberFormatException e)
        {
            m_Offset = 0;
            m_OffsetId = offset;
            // checkAttributes() will make sure m_OffsetId resolves to a <data> element with type="int"
        }
    }

    private void setOffsetfrom(String offsetfrom)
    {
        m_OffsetfromFixed = true;                                   // @A1A
        // Handle null or empty string
        if (offsetfrom == null || offsetfrom.equals(""))
        {
            m_Offsetfrom = -1;                                      // @A1A
            m_OffsetfromId = null;
            return;
        }

        try                                                         // @A1A
        {                                                           // @A1A
            m_Offsetfrom = Integer.parseInt(offsetfrom);            // @A1A
            m_OffsetfromId = null;                                  // @A1A
        }                                                           // @A1A
        catch (NumberFormatException e)                             // @A1A
        {                                                           // @A1A
            m_Offsetfrom = 0;                                       // @A1A
            m_OffsetfromId = offsetfrom;
            // checkAttributes() will make sure m_OffsetfromId resolves to a document element that
            //        an ancestor of this node.
        }                                                           // @A1A
    }

    private void setOutputsize(String outputsize)
    {
        // Handle null or empty string
        if (outputsize == null || outputsize.equals(""))
        {
            m_Outputsize = 0;
            return;
        }

        // Try to parse an integer from the attribute value
        try
        {
            m_Outputsize = Integer.parseInt(outputsize);
            m_OutputsizeId = null;
        }
        // If value is not an integer, it must be an element name
        // checkAttributes() will be called later to verify the element name
        catch (NumberFormatException e)
        {
            m_Outputsize = 0;
            m_OutputsizeId = outputsize;
            // checkAttributes() will make sure m_OutputsizeId resolves to a <data> element with type="int"
        }
    }

    private void setPassby(String passby)                           // @B1A
    {                                                               // @B1A
        // Handle null or empty string
        if (passby == null || passby.equals(""))                    // @B1A
        {                                                           // @B1A
            m_PassbyStr = null;                                     // @B1A
            m_Passby = ProgramParameter.PASS_BY_REFERENCE;          // @B1A
            return;                                                 // @B1A
        }                                                           // @B1A

        // Save the attribute value
        m_PassbyStr = passby;                                       // @B1A
        if ( m_PassbyStr.equals("value") )                          // @B1A
        {                                                           // @B1A
            m_Passby = ProgramParameter.PASS_BY_VALUE;              // @B1A
        }                                                           // @B1A
        else // Either passby="reference" was specified or defaulted   @B1A
        {                                                           // @B1A
            m_Passby = ProgramParameter.PASS_BY_REFERENCE;          // @B1A
        }                                                           // @B1A
    }                                                               // @B1A


    private void setBidiStringType(String type)                     // @C9A
    {
        // Handle null, empty string, or DEFAULT.
        if (type == null || type.equals("") || type.equals("DEFAULT"))
        {
            m_BidistringtypeStr = null;                             // @C9A
            m_Bidistringtype = BidiStringType.DEFAULT;              // @C9A
            return;                                                 // @C9A
        }                                                           // @C9A

        // Save the attribute value
        m_BidistringtypeStr = type;                                 // @C9A
        Integer bidistringtypeInt = (Integer)getBidiTypeMap().get(m_BidistringtypeStr);

        if (bidistringtypeInt == null) {
          m_Bidistringtype = BidiStringType.DEFAULT;
          if (Trace.isTraceOn()) {
            Trace.log(Trace.PCML, "[Warning]: Value of 'bidistringtype' attribute is not recognized: "+ m_BidistringtypeStr);
          }
        }
        else m_Bidistringtype = bidistringtypeInt.intValue();
    }

    private static Hashtable getBidiTypeMap()
    {
      if (bidiTypeMap_ == null)
      {
        synchronized (PcmlData.class)
        {
          if (bidiTypeMap_ == null)
          {
            bidiTypeMap_ = new Hashtable(10);

            bidiTypeMap_.put("DEFAULT", new Integer(BidiStringType.DEFAULT));
            bidiTypeMap_.put("NONE",    new Integer(BidiStringType.NONE));
            bidiTypeMap_.put("ST4",     new Integer(BidiStringType.ST4));
            bidiTypeMap_.put("ST5",     new Integer(BidiStringType.ST5));
            bidiTypeMap_.put("ST6",     new Integer(BidiStringType.ST6));
            bidiTypeMap_.put("ST7",     new Integer(BidiStringType.ST7));
            bidiTypeMap_.put("ST8",     new Integer(BidiStringType.ST8));
            bidiTypeMap_.put("ST9",     new Integer(BidiStringType.ST9));
            bidiTypeMap_.put("ST10",    new Integer(BidiStringType.ST10));
            bidiTypeMap_.put("ST11",    new Integer(BidiStringType.ST11));
          }
        }
      }
      return bidiTypeMap_;
    }

    private void setPrecision(String precision)
    {
        // Handle null or empty string
        if (precision == null || precision.equals(""))
        {
            m_Precision = 0;
            return;
        }

        // Try to parse an integer from the attribute value
        try
        {
            m_Precision = Integer.parseInt(precision);
        }
        // If value is not an integer, set the precision to -1
        // checkAttributes() log an error that the precision is invalid.
        catch (NumberFormatException e)
        {
            // checkAttributes() will add a PcmlSpecificationException
            m_Precision = -1;
        }
    }

    private void setStruct(String struct)
    {
        // Handle null or empty string
        if (struct == null || struct.equals(""))
        {
            m_StructId = null;
            return;
        }

        // Save the attribute value
        m_StructId = struct;
        // checkAttributes() will make sure m_StructId resolves to a document element
    }

    private void setType(String type)
    {
        m_TypeStr = type;

        // char | int | packed | zoned | float | byte | struct
        //      | date | time | timestamp
        if (type.equals("char"))
            m_Type = CHAR;
        else if (type.equals("int"))
            m_Type = INT;
        else if (type.equals("packed"))
            m_Type = PACKED;
        else if (type.equals("zoned"))
            m_Type = ZONED;
        else if (type.equals("float"))
            m_Type = FLOAT;
        else if (type.equals("byte"))
            m_Type = BYTE;
        else if (type.equals("struct"))
            m_Type = STRUCT;
        else if (type.equals("date"))
            m_Type = DATE;
        else if (type.equals("time"))
            m_Type = TIME;
        else if (type.equals("timestamp"))
            m_Type = TIMESTAMP;


        else  // none of the above
            // checkattributes() will add a pcml specification error.
            m_Type = UNSUPPORTED;
    }

    private void setTrim(String trimEnd)                            // @D1A
    {                                                               // @D1A
        // Handle null or empty string                              // @D1A
        if (trimEnd == null || trimEnd.equals(""))                  // @D1A
        {                                                           // @D1A
            m_TrimStr = null;                                       // @D1A
            return;                                                 // @D1A
        }                                                           // @D1A

        // Save the attribute value
        m_TrimStr = trimEnd;                                        // @D1A
    }

    private void setCharType(String charType)                       // @D2A
    {                                                               // @D2A
        // Handle null or empty string                              // @D2A
        if (charType == null || charType.equals(""))                // @D2A
        {                                                           // @D2A
            m_CharType = null;                                      // @D2A
            return;                                                 // @D2A
        }                                                           // @D2A

        // Save the attribute value
        m_CharType = charType;                                      // @D2A
    }

    private void setKeyField(String keyField)
    {
        // Handle null or empty string
        if (keyField == null || keyField.equals(""))
        {
            m_KeyField = false;
            return;
        }

        // Save the attribute value
        m_KeyFieldStr = keyField;
        if (keyField.equalsIgnoreCase("true")) m_KeyField = true;
        else m_KeyField = false;
    }

    private void setDateFormat(String format)
    {
        // Handle null or empty string
        if (format == null || format.equals(""))
        {
            m_DateFormat = null;
            return;
        }

        // Save the attribute value
        m_DateFormat = format;
    }

    private void setDateSeparator(String separator)
    {
        // Handle null or empty string
        if (separator == null || separator.equals(""))
        {
            m_DateSeparator = null;
            return;
        }

        // Save the attribute value
        m_DateSeparator = separator;
    }

    private void setTimeFormat(String format)
    {
        // Handle null or empty string
        if (format == null || format.equals(""))
        {
            m_TimeFormat = null;
            return;
        }

        // Save the attribute value
        m_TimeFormat = format;
    }

    private void setTimeSeparator(String separator)
    {
        // Handle null or empty string
        if (separator == null || separator.equals(""))
        {
            m_TimeSeparator = null;
            return;
        }

        // Save the attribute value
        m_TimeSeparator = separator;
    }

    protected void checkAttributes()
    {
        //String resolvedName = null;
        PcmlDocNode resolvedNode = null;

        super.checkAttributes();

        // Verify the count= attribute
        // If an integer was specified for the count, no checking is needed.
        // If a document element ID was was specified, make sure
        // it resolves to a <data> element with type="int".
        if (m_CountId != null)
        {
            resolvedNode = resolveRelativeNode(m_CountId);
            if (resolvedNode == null)
            {
                getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_NOT_FOUND, new Object[] {makeQuotedAttr("count", m_CountId), getNameForException()} );
            }
            else
            {
                if (resolvedNode instanceof PcmlData)
                {
                    if ( ((PcmlData)resolvedNode).getDataType() != PcmlData.INT )
                    {
                        getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_WRONG_NODETYPE, new Object[] {makeQuotedAttr("count", m_CountId), resolvedNode.getQualifiedName(), "<data>", getNameForException()} );
                    }
                }
                else
                {
                    getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_WRONG_DATATYPE, new Object[] {makeQuotedAttr("count", m_CountId), resolvedNode.getQualifiedName(), "type=\"int\"", getNameForException()} );
                }
            }
        }
        else
        // Do not allow count= to be a literal value that is negative
        if (m_Count < 0)
        {
            getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("count", m_Count), getBracketedTagName(), getNameForException()} ); // @A1C
        }


        // Verify the ccsid= attribute
        // If an integer was specified for the ccsid, no checking is needed.
        // If a document element ID was was specified, make sure
        // it resolves to a <data> element with type="int".
        if (m_IsRfml && m_CcsidWasSpecified && (getDataType() != CHAR))   // @D0A
        {
          getDoc().addPcmlSpecificationError(DAMRI.ATTRIBUTE_NOT_ALLOWED, new Object[] {makeQuotedAttr("ccsid",  getAttributeValue("ccsid")), makeQuotedAttr("type", getDataTypeString()), getBracketedTagName(), getNameForException()} );
        }
        if (m_CcsidId != null)
        {
            resolvedNode = resolveRelativeNode(m_CcsidId);
            if (resolvedNode == null)
            {
                getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_NOT_FOUND, new Object[] {makeQuotedAttr("ccsid", m_CcsidId), getNameForException()} );
            }
            else
            {
                if (resolvedNode instanceof PcmlData)
                {
                    if ( ((PcmlData)resolvedNode).getDataType() != PcmlData.INT )
                    {
                        getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_WRONG_NODETYPE, new Object[] {makeQuotedAttr("ccsid", m_CcsidId), resolvedNode.getQualifiedName(), "<data>", getNameForException()} );
                    }
                }
                else
                {
                    getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_WRONG_DATATYPE, new Object[] {makeQuotedAttr("ccsid", m_CcsidId), resolvedNode.getQualifiedName(), "type=\"int\"", getNameForException()} );
                }
            }
        }
        else
        // Do not allow ccsid= to be a literal value that is negative or greater than 65535.   @D0C
        if (m_Ccsid < 0 || m_Ccsid > 65535)  // @D0C - added check for >65535.
        {
            getDoc().addPcmlSpecificationError(DAMRI.DATA_LENGTH_OUT_OF_RANGE, new Object[] {new Integer(m_Ccsid), new Integer(0), new Integer(65535), getBracketedTagName(), getNameForException()} );
        }


        // Verify the init= attribute
        if (getInit() != null)
        {
          switch (getDataType())
          {
            default:
              try
              {
                TimeZone serverTimeZone = getDoc().getTimeZone(); 
                PcmlDataValues.convertValue((Object) getInit(), getDataType(), getLength(), getPrecision(), getNameForException(), serverTimeZone);
              }
              catch (Exception e)
              {
                getDoc().addPcmlSpecificationError(DAMRI.INITIAL_VALUE_ERROR, new Object[] {getInit(), getBracketedTagName(), getNameForException()} );
              }
              break;
          }

        }



        // Verify the length= attribute
        // If an integer was specified for the length, no checking is needed.
        // If a document element ID was was specified, make sure
        // it resolves to a <data> element with type="int".
        if (m_LengthId != null)
        {
            switch (getDataType())
            {
                case CHAR:
                case BYTE:
                    break;
                default:
                    getDoc().addPcmlSpecificationError(DAMRI.ATTRIBUTE_NOT_ALLOWED, new Object[] {makeQuotedAttr("length",  getAttributeValue("length")), getDataTypeString(), getBracketedTagName(), getNameForException()} );
            }


            resolvedNode = resolveRelativeNode(m_LengthId);
            if (resolvedNode == null)
            {
                getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_NOT_FOUND, new Object[] {makeQuotedAttr("length", m_LengthId), getNameForException()} );
            }
            else
            {
                if (resolvedNode instanceof PcmlData)
                {
                    if ( ((PcmlData)resolvedNode).getDataType() != PcmlData.INT )
                    {
                        getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_WRONG_NODETYPE, new Object[] {makeQuotedAttr("length", m_LengthId), resolvedNode.getQualifiedName(), "<data>", getNameForException()} );
                    }
                }
                else
                {
                    getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_WRONG_DATATYPE, new Object[] {makeQuotedAttr("length", m_LengthId), resolvedNode.getQualifiedName(), "type=\"int\"", getNameForException()} );
                }
            }
        }
        else  // an integer literal was specified for the 'length' attribute
        {
            // Verify the integer literal specified for length.
            if (m_Length < 0)
            {
                getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_SYNTAX, new Object[] {makeQuotedAttr("length", getAttributeValue("length")), "type=\"int\"", getBracketedTagName(), getNameForException()} );
            }
            else
            {
                switch (getDataType())
                {
                    case CHAR:
                    case BYTE:
                        if ( m_Length < 0 || m_Length > (MAX_STRING_LENGTH) )
                        {
                            getDoc().addPcmlSpecificationError(DAMRI.DATA_LENGTH_OUT_OF_RANGE, new Object[] {new Integer(m_Length), new Integer(0), new Integer(PcmlData.MAX_STRING_LENGTH), getBracketedTagName(), getNameForException()} );

                        }
                        break;

                    case INT:
                        if (m_Length != 2 && m_Length != 4 && m_Length != 8)    // @C4C
                        {
                            getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("length", m_Length), getBracketedTagName(), getNameForException()} );
                        }
                        break;

                    case PACKED:
                    case ZONED:
                        if (m_Length < 1 || m_Length > 31)
                        {
                            getDoc().addPcmlSpecificationError(DAMRI.DATA_LENGTH_OUT_OF_RANGE, new Object[] {new Integer(m_Length), new Integer(1), new Integer(31), getBracketedTagName(), getNameForException()} );
                        }
                        break;

                    case FLOAT:
                        if (m_Length != 4 && m_Length != 8)
                        {
                            getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("length", m_Length), getBracketedTagName(), getNameForException()} );
                        }
                        break;

                    case STRUCT:
                    case DATE:
                    case TIME:
                    case TIMESTAMP:
                        if ( getAttributeValue("length") != null
                         && !getAttributeValue("length").equals("") )
                        {
                            getDoc().addPcmlSpecificationError(DAMRI.ATTRIBUTE_NOT_ALLOWED, new Object[] {makeQuotedAttr("length",  getAttributeValue("length")), makeQuotedAttr("type",  getAttributeValue("type")), getBracketedTagName(), getNameForException()} );
                        }
                        break;

                    default:  // none of the above
                      getDoc().addPcmlSpecificationError(DAMRI.BAD_DATA_TYPE, new Object[] {makeQuotedAttr("type",  getAttributeValue("type")), getBracketedTagName(), getNameForException()} );

                }

                // Check for presence of 'length' attribute.
                if (!m_LengthWasSpecified &&
                    getDataType() != STRUCT &&
                    getDataType() != DATE &&
                    getDataType() != TIME &&
                    getDataType() != TIMESTAMP )
                {
                  if (m_IsRfml || ProgramCallDocument.exceptionIfParseError_) {
                    getDoc().addPcmlSpecificationError(DAMRI.NO_LENGTH, new Object[] {makeQuotedAttr("length", null), getBracketedTagName(), getNameForException()} );
                  }
                  else
                  {
                    if (Trace.isTraceOn()) {
                      Trace.log(Trace.PCML, "[Warning]: 'length' attribute was not specified in "+ getBracketedTagName() +" element: "+ getNameForException());
                    }
                  }
                }

                // Extra logic for RFML.                                      @D0A
                if (m_IsRfml)
                {
                  // If type="struct", the 'struct' attribute is required.
                  if (getDataType() == STRUCT)
                  {
                    if (getAttributeValue("struct") == null ||
                        getAttributeValue("struct").equals(""))
                    {
                      getDoc().addPcmlSpecificationError(DAMRI.NO_STRUCT, new Object[] {makeQuotedAttr("struct", null), getBracketedTagName(), getNameForException()} );
                    }
                  }

                  // If type="struct", the 'struct' attribute is required.
                  else if (getDataType() == DATE || getDataType() == TIME ||
                           getDataType() == TIMESTAMP)
                  {} // 'length' is not required

                  // Otherwise, the 'length' attribute is required.
                  else if (!m_LengthWasSpecified)
                  {
                    getDoc().addPcmlSpecificationError(DAMRI.NO_LENGTH, new Object[] {makeQuotedAttr("length", null), getBracketedTagName(), getNameForException()} );
                  }
                }
            }
        }

        // Verify the offset= attribute
        // If an integer was specified for the offset, no checking is needed.
        // If a document element ID was was specified, make sure
        // it resolves to a <data> element with type="int".
        if (m_OffsetId != null)
        {
            resolvedNode = resolveRelativeNode(m_OffsetId);
            if (resolvedNode == null)
            {
                getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_NOT_FOUND, new Object[] {makeQuotedAttr("offset", m_OffsetId), getNameForException()} );
            }
            else
            {
                if (resolvedNode instanceof PcmlData)
                {
                    if ( ((PcmlData)resolvedNode).getDataType() != PcmlData.INT )
                    {
                        getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_WRONG_NODETYPE, new Object[] {makeQuotedAttr("offset", m_OffsetId), resolvedNode.getQualifiedName(), "<data>", getNameForException()} );
                    }
                }
                else
                {
                    getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_WRONG_DATATYPE, new Object[] {makeQuotedAttr("offset", m_OffsetId), resolvedNode.getQualifiedName(), "type=\"int\"", getNameForException()} );
                }
            }
        }
        else
        // Do not allow offset= to be a literal value that is negative
        if (m_Offset < 0)
        {
            getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("offset", m_Offset), getBracketedTagName(), getNameForException()} ); // @A1C
        }

        // Verify the offsetfrom= attribute
        // If a document element ID was was specified, make sure
        // it resolves to a document element that is an ancestor of this element.
        if (m_OffsetfromId != null)
        {
            resolvedNode = resolveRelativeNode(m_OffsetfromId);
            if (resolvedNode == null)
            {
                getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_NOT_FOUND, new Object[] {makeQuotedAttr("offsetfrom", m_OffsetfromId), getNameForException()} );
            }
            else
            {
                String qName = getQualifiedName();
                if (qName.equals(""))
                {
                    qName = getNameForException();
                }
                String qNameResolved = resolvedNode.getQualifiedName();
                if (!qName.startsWith(qNameResolved + "."))
                {
                    getDoc().addPcmlSpecificationError(DAMRI.OFFSETFROM_NOT_FOUND, new Object[] {m_OffsetfromId, getNameForException()} );
                }
            }
        }
        else
        // Do not allow offsetfrom= to be a literal value that is negative
        if (m_Offsetfrom < -1)                                      // @A1A
        {                                                           // @A1A
            getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("offsetfrom", m_Offsetfrom), getBracketedTagName(), getNameForException()} ); // @A1A
        }                                                           // @A1A

        // Verify the outputsize= attribute
        // If an integer was specified for the offset, make sure it is in valid range.
        // If a document element ID was was specified, make sure
        // it resolves to a <data> element with type="int".
        if (m_OutputsizeId != null)
        {
            resolvedNode = resolveRelativeNode(m_OutputsizeId);
            if (resolvedNode == null)
            {
                getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_NOT_FOUND, new Object[] {makeQuotedAttr("outputsize", m_OutputsizeId), getNameForException()} );
            }
            else
            {
                if (resolvedNode instanceof PcmlData)
                {
                    if ( ((PcmlData)resolvedNode).getDataType() != PcmlData.INT )
                    {
                        getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_WRONG_NODETYPE, new Object[] {makeQuotedAttr("outputsize", m_OutputsizeId), resolvedNode.getQualifiedName(), "<data>", getNameForException()} );
                    }
                }
                else
                {
                    getDoc().addPcmlSpecificationError(DAMRI.ATTR_REF_WRONG_DATATYPE, new Object[] {makeQuotedAttr("outputsize", m_OutputsizeId), resolvedNode.getQualifiedName(), "type=\"int\"", getNameForException()} );
                }
            }
        }
        else
        // Do not allow offset= to be a literal value that is negative
        if (m_Outputsize < 0)
        {
            getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("outputsize", m_Outputsize), getBracketedTagName(), getNameForException()} ); // @A1C
        }


        // Verify the precision= attribute
        if (getAttributeValue("precision") != null
        &&  !getAttributeValue("precision").equals("") ) // @++C
        {
            switch (getDataType())
            {
                // precision= is not allowed for these data types
                case CHAR:
                case BYTE:
                case FLOAT:
                case STRUCT:
                case DATE:
                case TIME:
                case TIMESTAMP:
                       getDoc().addPcmlSpecificationError(DAMRI.ATTRIBUTE_NOT_ALLOWED, new Object[] {makeQuotedAttr("precision",  getAttributeValue("precision")), makeQuotedAttr("type", getDataTypeString()), getBracketedTagName(), getNameForException()} );
                    break;

                // For type=int, precision= must be 15 or 16, or 31 or 32, or 63 or 64, depending on length=
                case INT:
                    if (m_Length == 2)
                    {
                        if (m_Precision != 15 && m_Precision != 16)
                        {
                            getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("precision",  getAttributeValue("precision")), getBracketedTagName(), getNameForException()} );
                        }
                    }
                    if (m_Length == 4)
                    {
                        if (m_Precision != 31 && m_Precision != 32)
                        {
                            getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("precision",  getAttributeValue("precision")), getBracketedTagName(), getNameForException()} );
                        }
                    }
                    if (m_Length == 8)                              // @C4A
                    {                                               // @C4A
                        if (m_Precision != 63 && m_Precision != 64) // @C4A
                        {                                           // @C4A
                            getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("precision",  getAttributeValue("precision")), getBracketedTagName(), getNameForException()} ); // @C4A
                        }                                           // @C4A
                    }                                               // @C4A
                    break;

                // For type=packed and type=zoned,
                // precision= must be >= 0 and <= the data length (length=)
                case PACKED:
                case ZONED:
                    if (m_Precision < 0 || m_Precision > m_Length)
                    {
                        getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("precision",  getAttributeValue("precision")), getBracketedTagName(), getNameForException()} );
                    }
                    break;

            }

        }


        // Verify the struct= attribute
        if (m_StructId != null)
        {
            if (getDataType() != STRUCT)
            {
                   getDoc().addPcmlSpecificationError(DAMRI.ATTRIBUTE_NOT_ALLOWED, new Object[] {makeQuotedAttr("struct",  getAttributeValue("struct")), makeQuotedAttr("type", getDataTypeString()), getBracketedTagName(), getNameForException()} );
            }
        }



        // Verify the minvrm= attribute
        if (m_Minvrm != null)
        {
            m_MinvrmInt = validateVRM(m_Minvrm);
            if (m_MinvrmInt <= 0)
            {
                getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("minvrm", m_Minvrm), getBracketedTagName(), getNameForException()} ); // @A1C
            }
        }


        // Verify the maxvrm= attribute
        if (m_Maxvrm != null)
        {
            m_MaxvrmInt = validateVRM(m_Maxvrm);
            if (m_MaxvrmInt <= 0)
            {
                getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("maxvrm", m_Maxvrm), getBracketedTagName(), getNameForException()} ); // @A1C
            }
        }

        // Verify the passby= attribute
        if (m_PassbyStr != null)                                    // @B1A
        {                                                           // @B1A
            // Only allow this attribute when the pcml version is 2.0 or higher (e.g. <pcml version="2.0">)
            if ( getDoc().getVersion().compareTo("2.0") < 0 )       // @B1A
            {                                                       // @B1A
                getDoc().addPcmlSpecificationError(DAMRI.BAD_PCML_VERSION, new Object[] {makeQuotedAttr("passby", m_PassbyStr), "2.0", getBracketedTagName(), getNameForException()} ); // @B1A @C9C
            }                                                       // @B1A

            // Only allow this attribute when it is a child of <program>
            if ( !(getParent() instanceof PcmlProgram) )            // @B1A
            {                                                       // @B1A
                getDoc().addPcmlSpecificationError(DAMRI.NOT_CHILD_OF_PGM, new Object[] {makeQuotedAttr("passby", m_PassbyStr), getBracketedTagName(), getNameForException()} ); // @B1A
            }                                                       // @B1A
        }                                                           // @B1A

        // Verify the bidistringtype= attribute
        if (m_BidistringtypeStr != null)                            // @C9A
        {                                                           // @C9A
            // Only allow this attribute when the pcml version is 3.0 or higher (e.g. <pcml version="3.0">)
            if ( getDoc().getVersion().compareTo("3.0") < 0 )       // @C9A
            {                                                       // @C9A
                getDoc().addPcmlSpecificationError(DAMRI.BAD_PCML_VERSION, new Object[] {makeQuotedAttr("bidistringtype", m_BidistringtypeStr), "3.0", getBracketedTagName(), getNameForException()} ); // @C9A
            }                                                       // @C9A
        }

        // Verify the trim= attribute
        if (m_TrimStr != null)                                      // @D1A
        {                                                           // @D1A
            // Only allow this attribute when the pcml version is 4.0 or higher (e.g. <pcml version="4.0">)
            if ( getDoc().getVersion().compareTo("4.0") < 0 )       // @D1A
            {                                                       // @D1A
                getDoc().addPcmlSpecificationError(DAMRI.BAD_PCML_VERSION, new Object[] {makeQuotedAttr("trim", m_TrimStr), "4.0", getBracketedTagName(), getNameForException()} ); // @D1A
            }                                                       // @D1A
        }

        // Verify the chartype= attribute
        if (m_CharType != null)                                     // @D2A
        {                                                           // @D2A
            // Only allow this attribute when the pcml version is 4.0 or higher (e.g. <pcml version="4.0">)
            if ( getDoc().getVersion().compareTo("4.0") < 0 )       // @D2A
            {                                                       // @D2A
                getDoc().addPcmlSpecificationError(DAMRI.BAD_PCML_VERSION, new Object[] {makeQuotedAttr("chartype", m_CharType), "4.0", getBracketedTagName(), getNameForException()} ); // @D2A
            }                                                       // @D2A
            else
            {
                if (getDataType() != CHAR)                             // @D2A
                {
                  getDoc().addPcmlSpecificationError(DAMRI.ATTRIBUTE_NOT_ALLOWED, new Object[] {makeQuotedAttr("chartype",  getAttributeValue("chartype")), makeQuotedAttr("type", getDataTypeString()), getBracketedTagName(), getNameForException()} );
                }
            }
        }

        // Verify the keyfield= attribute  (applies only to RFML)
        if (m_KeyFieldStr != null)
        {
            // Only allow this attribute when the rfml version is 5.0 or higher (e.g. <rfml version="5.0">)
            if (!m_IsRfml)  // if not rfml, then assume it's pcml
            {
              getDoc().addPcmlSpecificationError(DAMRI.ATTRIBUTE_NOT_ALLOWED, new Object[] {makeQuotedAttr("keyfield",  getAttributeValue("keyfield")), makeQuotedAttr("pcml", getDataTypeString()), getBracketedTagName(), getNameForException()} );
            }
            if ( getDoc().getVersion().compareTo("5.0") < 0 )
            {
                getDoc().addPcmlSpecificationError(DAMRI.BAD_PCML_VERSION, new Object[] {makeQuotedAttr("keyfield", m_KeyFieldStr), "5.0", getBracketedTagName(), getNameForException()} );
            }
        }

        // Verify the dateformat= attribute
        if (m_DateFormat != null)
        {
            // Only allow this attribute when the pcml version is 6.0 or higher (e.g. <pcml version="6.0">), and only for type "date".
            if ( getDoc().getVersion().compareTo("6.0") < 0 )
            {
                getDoc().addPcmlSpecificationError(DAMRI.BAD_PCML_VERSION, new Object[] {makeQuotedAttr("dateformat", m_DateFormat), "6.0", getBracketedTagName(), getNameForException()} );
            }

            switch (getDataType())
            {
              case DATE:
                // Check that it's a recognized format. Note: This is NOT already checked by the parser.
                if (!AS400Date.validateFormat(AS400Date.toFormat(m_DateFormat))) {
                  getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("dateformat",  getAttributeValue("dateformat")), getBracketedTagName(), getNameForException()} );
                }
                break;

              default:  // type is not DATE
                // dateformat= is not allowed for any other data type
                getDoc().addPcmlSpecificationError(DAMRI.ATTRIBUTE_NOT_ALLOWED, new Object[] {makeQuotedAttr("dateformat",  getAttributeValue("dateformat")), makeQuotedAttr("type", getDataTypeString()), getBracketedTagName(), getNameForException()} );
                break;
            }
        }

        // Verify the dateseparator= attribute
        if (m_DateSeparator != null)
        {
            // Only allow this attribute when the pcml version is 6.0 or higher (e.g. <pcml version="6.0">), and only for type "date".
            if ( getDoc().getVersion().compareTo("6.0") < 0 )
            {
                getDoc().addPcmlSpecificationError(DAMRI.BAD_PCML_VERSION, new Object[] {makeQuotedAttr("dateseparator", m_DateSeparator), "6.0", getBracketedTagName(), getNameForException()} );
            }

            switch (getDataType())
            {
              case DATE:
                // Check that it's a recognized separator name.
                if (!isValidSeparatorName(m_DateSeparator)) {
                  Trace.log(Trace.PCML, "Separator name '" + m_DateSeparator + "' is not recognized.");
                  getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("dateseparator",  getAttributeValue("dateseparator")), getBracketedTagName(), getNameForException()} );
                }
                break;

              default:  // type is not DATE
                // dateseparator= is not allowed for any other data type
                getDoc().addPcmlSpecificationError(DAMRI.ATTRIBUTE_NOT_ALLOWED, new Object[] {makeQuotedAttr("dateseparator",  getAttributeValue("dateseparator")), makeQuotedAttr("type", getDataTypeString()), getBracketedTagName(), getNameForException()} );
                break;
            }
        }

        // Verify the timeformat= attribute
        if (m_TimeFormat != null)
        {
            // Only allow this attribute when the pcml version is 6.0 or higher (e.g. <pcml version="6.0">), and only for type "time".
            if ( getDoc().getVersion().compareTo("6.0") < 0 )
            {
                getDoc().addPcmlSpecificationError(DAMRI.BAD_PCML_VERSION, new Object[] {makeQuotedAttr("timeformat", m_TimeFormat), "6.0", getBracketedTagName(), getNameForException()} );
            }

            switch (getDataType())
            {
              case TIME:
                // Check that it's a recognized format. Note: This is NOT already checked by the parser.
                if (!AS400Time.validateFormat(AS400Time.toFormat(m_TimeFormat))) {
                  getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("timeformat",  getAttributeValue("timeformat")), getBracketedTagName(), getNameForException()} );
                }
                break;

              default:  // type is not TIME
                // timeformat= is not allowed for any other data type
                getDoc().addPcmlSpecificationError(DAMRI.ATTRIBUTE_NOT_ALLOWED, new Object[] {makeQuotedAttr("timeformat",  getAttributeValue("timeformat")), makeQuotedAttr("type", getDataTypeString()), getBracketedTagName(), getNameForException()} );
                break;
            }
        }

        // Verify the timeseparator= attribute
        if (m_TimeSeparator != null)
        {
            // Only allow this attribute when the pcml version is 6.0 or higher (e.g. <pcml version="6.0">), and only for type "time".
            if ( getDoc().getVersion().compareTo("6.0") < 0 )
            {
                getDoc().addPcmlSpecificationError(DAMRI.BAD_PCML_VERSION, new Object[] {makeQuotedAttr("timeseparator", m_TimeSeparator), "6.0", getBracketedTagName(), getNameForException()} );
            }

            switch (getDataType())
            {
              case TIME:
                // Check that it's a recognized separator name.
                if (!isValidSeparatorName(m_TimeSeparator)) {
                  Trace.log(Trace.PCML, "Separator name '" + m_TimeSeparator + "' is not recognized.");
                  getDoc().addPcmlSpecificationError(DAMRI.BAD_ATTRIBUTE_VALUE, new Object[] {makeQuotedAttr("timeseparator",  getAttributeValue("timeseparator")), getBracketedTagName(), getNameForException()} );
                }
                break;

              default:  // type is not TIME
                // timeseparator= is not allowed for any other data type
                getDoc().addPcmlSpecificationError(DAMRI.ATTRIBUTE_NOT_ALLOWED, new Object[] {makeQuotedAttr("timeseparator",  getAttributeValue("timeseparator")), makeQuotedAttr("type", getDataTypeString()), getBracketedTagName(), getNameForException()} );
                break;
            }
        }

    }


    // Check if a string is a valid IBM i system VRM
    // Allowed syntax:
    //    "VxRyMz"
    //
    // where:
    //    "V", "R" and "M" are literal characters
    //    x is an integer from 1 to 255
    //    y is an integer from 0 to 255
    //    z is an integer from 0 to 255
    //
    // If valid, a positive integer is returned. This is the value generated
    // by com.ibm.as400.access.AS400.generateVRM().
    // If invalid, -1 is returned.
    private static final String vrmDelimChars = "VRM";
    static int validateVRM(String vrmStr)
    {
        StringTokenizer vrmTokens, vrmDelimiters;

        int as400vrm = -1;
        int[] vrm = new int[] {-1, -1, -1};

        vrmTokens = new StringTokenizer(vrmStr, vrmDelimChars, true);
        int vrmTokenNbr = 0;
        vrmDelimiters = new StringTokenizer(vrmDelimChars, vrmDelimChars, true);

        if (vrmTokens.countTokens() == 6)
        {
            while (vrmDelimiters.hasMoreTokens())
            {
                if (vrmTokens.nextToken().equals(vrmDelimiters.nextToken()))
                {
                    try
                    {
                        vrm[vrmTokenNbr] = Integer.parseInt(vrmTokens.nextToken());
                        vrmTokenNbr++;
                    }
                    catch (NumberFormatException e)
                    { }
                }
            }
        }

        // If all of the integers are within allowed ranges
        // generate the VRM integer to be returned.
        if ( vrm[0] >= 1 && vrm[0] <= 255
          && vrm[1] >= 0 && vrm[0] <= 255
          && vrm[2] >= 0 && vrm[0] <= 255)
        {
            as400vrm = AS400.generateVRM(vrm[0],vrm[1],vrm[2]);
        }

        return as400vrm;
    }

}
