//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1994 - 1999.
//
//  File:       propbase.cxx
//
//  Contents:   Utility object containing implementation of base property
//
//  Classes:    CUtlProps
//
//  History:    10-28-97        danleg    Created from monarch uprops.cpp
//
//----------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include "propbase.hxx"



//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::CUtlProps, public
//
//  Synopsis:   Constructor for CUtlProps
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

CUtlProps::CUtlProps( DWORD dwFlags ) :
    _cPropSetDex(0),
    _cUPropSet(0),
    _cUPropSetHidden(0),
    _pUPropSet(0),
    _dwFlags(dwFlags),
    _xaUProp(),
    _cElemPerSupported(0),
    _xadwSupported(),
    _xadwPropsInError(),
    _xaiPropSetDex()
{
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::~CUtlProps, public
//
//  Synopsis:   Destructor for CUtlProps
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

CUtlProps::~CUtlProps()
{
    FreeMemory();
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::FillDefaultValues, public
//
//  Synopsis:   Fill all the default values.  Note that failure might leave
//              things only half done.  
//
//  Arguments:  [ulPropSetTarget]   - Propset to fill if only one
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

SCODE CUtlProps::FillDefaultValues( ULONG ulPropSetTarget )
{
    SCODE sc = S_OK;
    ULONG ulPropId;
    ULONG iNewDex;

    // Fill in all the actual values.
    // Note that the UPROP (with values) array may be a subset of the UPROPINFO array.
    // Only writable properties are in UPROP array.

    // Maybe restrict to a single PropSet if within valid range [0..._cUPropSet-1].
    // Otherwise do all propsets.
    ULONG iPropSet = (ulPropSetTarget < _cUPropSet) ? ulPropSetTarget : 0;

    for( ; iPropSet<_cUPropSet; iPropSet++)
    {
        iNewDex = 0;
        for(ulPropId=0; ulPropId<_pUPropSet[iPropSet].cUPropInfo; ulPropId++)
        {
            if ( _pUPropSet[iPropSet].pUPropInfo[ulPropId].dwFlags & 
                (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
            {
                // Initialize dwFlags element of UPropVal
                _xaUProp[iPropSet].pUPropVal[iNewDex].dwFlags = 0;

                VariantClear( &_xaUProp[iPropSet].pUPropVal[iNewDex].vValue );
                sc = GetDefaultValue(
                        iPropSet, 
                        _pUPropSet[iPropSet].pUPropInfo[ulPropId].dwPropId, 
                        &_xaUProp[iPropSet].pUPropVal[iNewDex].dwOption, 
                        &_xaUProp[iPropSet].pUPropVal[iNewDex].vValue );
                if ( FAILED(sc) )
                    return sc;
                iNewDex++;
            }
        }

        // We're through if restricting to single PropSet.
        if ( ulPropSetTarget < _cUPropSet )
            break;
    }
    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::GetPropertiesArgChk, public
//
//  Synopsis:   Initialize the buffers and check for E_INVALIDARG cases.
//              This routine is used by RowsetInfo, CommandProperties and 
//              IDBProperties
//
//  Arguments:  [cPropertySets]     - number of property sets
//              [rgPropertySets]    - property classes and ids
//              [pcProperties]      - count of structs returned [out]
//              [prgProperties]     - array of properties [out]
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

void CUtlProps::GetPropertiesArgChk
    (
    const ULONG         cPropertySets,      
    const DBPROPIDSET   rgPropertySets[],   
    ULONG*              pcProperties,       
    DBPROPSET**         prgProperties       
    )
{
    ULONG ul;

    // Initialize values
    if ( pcProperties )
        *pcProperties = 0;
    if ( prgProperties )
        *prgProperties = 0; 

    // Check Arguments
    if ( ((cPropertySets > 0) && !rgPropertySets) ||
        !pcProperties || !prgProperties )
        THROW( CException(E_INVALIDARG) );

    // New argument check for > 1 cPropertyIDs and NULL pointer for 
    // array of property ids.
    for(ul=0; ul<cPropertySets; ul++)
    {
        if ( rgPropertySets[ul].cPropertyIDs && 
            !(rgPropertySets[ul].rgPropertyIDs) )
            THROW( CException(E_INVALIDARG) );
    
        // Check for propper formation of DBPROPSET_PROPERTIESINERROR
        if ( (_dwFlags & ARGCHK_PROPERTIESINERROR) && 
            rgPropertySets[ul].guidPropertySet == DBPROPSET_PROPERTIESINERROR )
        {
            if ( (cPropertySets > 1) ||
                (rgPropertySets[ul].cPropertyIDs != 0) ||
                (rgPropertySets[ul].rgPropertyIDs != 0) )
                THROW( CException(E_INVALIDARG) );
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::SetPropertiesArgChk, public
//
//  Synopsis:   Initialize the buffers and check for E_INVALIDARG cases
//              NOTE: This routine is used by CommandProperties and 
//                    IDBProperties
//
//  Arguments:  [cPropertySets]     - count of structs returned
//              [rgPropertySets]    - array of propertysets
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

void CUtlProps::SetPropertiesArgChk
    (
    const ULONG     cPropertySets,      
    const DBPROPSET rgPropertySets[]    
    )
{
    ULONG   ul;

    // Argument Checking
    if ( cPropertySets > 0 && !rgPropertySets )
        THROW( CException(E_INVALIDARG) );

    // New argument check for > 1 cPropertyIDs and NULL pointer for 
    // array of property ids.
    for(ul=0; ul<cPropertySets; ul++)
    {
        if ( rgPropertySets[ul].cProperties &&
             !(rgPropertySets[ul].rgProperties) )
            THROW( CException(E_INVALIDARG) );
    }
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::GetProperties, public
//
//  Synopsis:   Collect the property information that the consumer is 
//              interested in.  If no restriction guids are supplied, 
//              return all known properties.
//
//              NOTE: If cProperties is 0, this function will return an array
//              of guids that had been previously set.  IF cProperties is non 0,
//              the routine will use the array of property guids passed in and
//              return informatino about the properties asked for only.
//
//              This routine is used by RowsetInfo and CommandProperties.
//
//  Arguments:  [cPropertySets]     - number of property setes
//              [rgPropertySets]    - property classes and ids
//              [pcProperties]      - count of structs returned [out]
//              [prgProperties]     - array of properties [out]
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

SCODE CUtlProps::GetProperties(
    const ULONG       cPropertySets,      
    const DBPROPIDSET rgPropertySets[],   
    ULONG*            pcProperties,       
    DBPROPSET**       prgProperties )
{
    UPROPVAL*  pUPropVal;
    ULONG      ulCurProp;
    ULONG      cTmpPropertySets = cPropertySets;
    SCODE      sc = S_OK;
    ULONG      ulSet = 0;
    ULONG      ulNext = 0;
    ULONG      cSets = 0;
    ULONG      cProps = 0;
    ULONG      ulProp = 0;
    DWORD      dwStatus = 0;
    DBPROP*    pProp = 0;
    UPROPINFO* pUPropInfo = 0;
    ULONG      ulCurSet;
    ULONG      iPropSet;

    // We need to have special handling for DBPROPSET_PROPERTIESINERROR.
    // Turn on a flags to indicate this mode and make cTmpPropertySets
    // appear to be 0.

    if ( (_dwFlags & ARGCHK_PROPERTIESINERROR) &&
         rgPropertySets && 
         (rgPropertySets[0].guidPropertySet == DBPROPSET_PROPERTIESINERROR) )
    {
        cTmpPropertySets = 0;
        dwStatus |= GETPROP_PROPSINERROR;
    }

    // If the consumer does not restrict the property sets by specify an
    // array of property sets and a cTmpPropertySets greater than 0, then we
    // need to make sure we have some to return

    if ( 0 == cTmpPropertySets )
    {
        // Determine the number of property sets supported

        cSets = _cUPropSet;
    }
    else
    {
        // Since special property set guids are not supported by
        // GetProperties, we can just use the count of property sets given to
        // us.

        cSets = cTmpPropertySets;
    }

    // If no properties set, then return

    if ( 0 == cSets )
        return S_OK;
                    
    // Allocate the DBPROPSET structures

    DBPROPSET * pPropSet = (DBPROPSET *) CoTaskMemAlloc( cSets * sizeof DBPROPSET );
    if ( 0 != pPropSet )
    {
        RtlZeroMemory( pPropSet, cSets * sizeof(DBPROPSET) );

        // Fill in the output array
        iPropSet = 0;
        for(ulSet=0; ulSet<cSets; ulSet++)
        {
            // Depending of if Property sets are specified store the
            // return property set.
            if ( cTmpPropertySets == 0 )
            {
                if ( _pUPropSet[ulSet].dwFlags & UPROPSET_HIDDEN )
                    continue;
                
                pPropSet[iPropSet].guidPropertySet = *(_pUPropSet[ulSet].pPropSet);
            }
            else
                pPropSet[iPropSet].guidPropertySet = rgPropertySets[ulSet].guidPropertySet;         

            iPropSet++;
        }
    }
    else
    {
        vqDebugOut(( DEB_ERROR, "Could not allocate DBPROPSET array for GetProperties\n" ));
        return E_OUTOFMEMORY;
    }

    TRY
    {
        // Process requested or derived Property sets
    
        iPropSet=0;
        for( ulSet = 0; ulSet < cSets; ulSet++ )
        {
            XCoMem<DBPROP> xProp;
            cProps  = 0;
            pProp   = 0;
            ulNext  = 0;
            dwStatus &= (GETPROP_ERRORSOCCURRED | GETPROP_VALIDPROP | GETPROP_PROPSINERROR);
    
            // Calculate the number of property nodes needed for this
            // property set.
    
            if ( 0 == cTmpPropertySets )
            {
                // If processing requesting all property sets, do not
                // return the hidden sets.
    
                if ( _pUPropSet[ulSet].dwFlags & UPROPSET_HIDDEN )
                    continue;
    
                cProps = _pUPropSet[ulSet].cUPropInfo;
    
                dwStatus |= GETPROP_ALLPROPIDS;
                ulCurSet = ulSet;
            }
            else
            {
                Win4Assert( ulSet == iPropSet );
    
                // If the count of PROPIDs is 0 or it is a special property set,
                // then the consumer is requesting all propids for this property
                // set.
    
                if ( 0 == rgPropertySets[ulSet].cPropertyIDs )
                {
                    dwStatus |= GETPROP_ALLPROPIDS;
    
                    // We have to determine if the property set is supported and
                    // if so the count of properties in the set.
    
                    if ( S_OK == GetIndexofPropSet( &(pPropSet[iPropSet].guidPropertySet),
                                                    &ulCurSet ) )
                    {
                        cProps += _pUPropSet[ulCurSet].cUPropInfo;
                    }
                    else
                    {
                        // Not Supported
    
                        dwStatus |= GETPROP_ERRORSOCCURRED;
                        goto NEXT_SET;
                    }
                }
                else
                {
                    cProps = rgPropertySets[ulSet].cPropertyIDs;
    //@TODO determine extra nodes needed for colids based on restriction array, it is
    //possible that the same restriction is used twice, thus using our other calculation method
    //would cause an overflow.
                    if (GetIndexofPropSet(&(pPropSet[iPropSet].guidPropertySet), &ulCurSet) != S_OK)
                    {
                        dwStatus |= GETPROP_NOTSUPPORTED;
                        dwStatus |= GETPROP_ERRORSOCCURRED;
                    }
                }
            }
    
            // Allocate DBPROP array
    
            if ( 0 == cProps )          //Possible with Hidden Passthrough sets
                goto NEXT_SET;
    
            pProp = (DBPROP *) CoTaskMemAlloc( cProps * sizeof(DBPROP) );
            xProp.Set( pProp );
    
            if ( 0 != pProp )
            {
                // Now that we have determined we can support the property set,
                // we need to gather current property values
    
                for( ulProp=0; ulProp < cProps; ulProp++ )
                {
                    // initialize the structure.  Memberwise initialization is
                    // faster than old memset code -- MDW
    
                    pProp[ulProp].dwPropertyID = 0;
                    pProp[ulProp].dwOptions = 0;
                    pProp[ulProp].dwStatus = 0;
                    RtlZeroMemory( &(pProp[ulProp].colid), sizeof DBID );
                    VariantInit( &(pProp[ulProp].vValue) );
    
                    if ( 0 != ( dwStatus & GETPROP_NOTSUPPORTED ) )
                    {
                        // Not supported, so we need to mark all as NOT_SUPPORTED
    
                        pProp[ulProp].dwPropertyID = rgPropertySets[ulSet].rgPropertyIDs[ulProp];
                        pProp[ulProp].dwStatus     = DBPROPSTATUS_NOTSUPPORTED;
                        continue;
                    }                   
    
                    DBPROP * pCurProp = &(pProp[ulNext]);
    
                    // Initialize Variant Value
    
                    pCurProp->dwStatus = DBPROPSTATUS_OK;
    
                    // Retrieve current value of properties
    
                    if ( 0 != ( dwStatus & GETPROP_ALLPROPIDS ) )
                    {
                        // Verify property is supported, if not do not return
    
                        if ( !TESTBIT(&(_xadwSupported[ulCurSet * _cElemPerSupported]), ulProp) )
                            continue;
    
                        // If we are looking for properties in error, then we
                        // should ignore all that are not in error.
    
                        if ( ( 0 != (dwStatus & GETPROP_PROPSINERROR) ) &&
                            !TESTBIT(&(_xadwPropsInError[ulCurSet * _cElemPerSupported]), ulProp) )
                            continue;
    
                        pUPropInfo = (UPROPINFO*)&(_pUPropSet[ulCurSet].pUPropInfo[ulProp]);
    
                        Win4Assert( pUPropInfo );
    
                        pCurProp->dwPropertyID = pUPropInfo->dwPropId;
                        pCurProp->colid = DB_NULLID;
    
                        // If the property is WRITEABLE or CHANGABLE and not
                        // inerror, then the value will be gotten from the
                        // UPROPVAL array, else it will be derive from the
                        // GetDefaultValue
    
                        if ( 0 != ( pUPropInfo->dwFlags &
                                    ( DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE ) ) )
                        {
                            pUPropVal = &(_xaUProp[ulCurSet].
                                        pUPropVal[GetUPropValIndex( ulCurSet,
                                                                    pCurProp->dwPropertyID )]);
                            Win4Assert( 0 != pUPropVal );
    
                            if ( 0 != ( pUPropVal->dwFlags & DBINTERNFLAGS_INERROR ) )
                            {
                                sc = GetDefaultValue( ulCurSet,
                                                      pUPropInfo->dwPropId, 
                                                      &(pCurProp->dwOptions),
                                                      &(pCurProp->vValue) );
    
                                if ( FAILED( sc ) )
                                {
                                    pCurProp->vValue.vt = VT_EMPTY;
                                    THROW( CException( sc ) );
                                }
                            }
                            else
                            {
                                pCurProp->dwOptions = pUPropVal->dwOption;
                                sc = VariantCopy( &(pCurProp->vValue),
                                                  &(pUPropVal->vValue) );
    
                                if ( FAILED( sc ) )
                                {
                                    pCurProp->vValue.vt = VT_EMPTY;
                                    THROW( CException( sc ) );
                                }
                            }
                        }
                        else
                        {
                            sc = GetDefaultValue( ulCurSet,
                                                  pUPropInfo->dwPropId, 
                                                  &(pCurProp->dwOptions),
                                                  &(pCurProp->vValue) );
                            if ( FAILED( sc ) )
                            {
                                pCurProp->vValue.vt = VT_EMPTY;
                                THROW( CException( sc ) );
                            }
                        }
        
                        // Return all Properties in Error with CONFLICT status
    
                        if ( 0 != ( dwStatus & GETPROP_PROPSINERROR ) )
                            pCurProp->dwStatus = DBPROPSTATUS_CONFLICTING;
                        
                        dwStatus |= GETPROP_VALIDPROP;
                    }
                    else
                    {
                        // Process Properties based on Restriction array.
    
                        pCurProp->dwPropertyID = rgPropertySets[ulSet].rgPropertyIDs[ulProp];
                        pCurProp->colid = DB_NULLID;
    
                        if ( S_OK == GetIndexofPropIdinPropSet( ulCurSet,
                                                                pCurProp->dwPropertyID, 
                                                                &ulCurProp ) )
                        {
                            // Supported
    
                            pUPropInfo = (UPROPINFO*)&(_pUPropSet[ulCurSet].pUPropInfo[ulCurProp]);
                            Win4Assert( 0 != pUPropInfo );
    
                            // If the property is WRITEABLE, then the value will
                            // be gotten from the UPROPVAL array, else it will be
                            // derive from the GetDefaultValue
    
                            if ( 0 != ( pUPropInfo->dwFlags &
                                        (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) ) )
                            {
                                pUPropVal = &(_xaUProp[ulCurSet].
                                    pUPropVal[GetUPropValIndex(ulCurSet, pCurProp->dwPropertyID)]);
                                Win4Assert( 0 != pUPropVal );
                            
                                pCurProp->dwOptions = pUPropVal->dwOption;
                                sc = VariantCopy(&(pCurProp->vValue), &(pUPropVal->vValue));
                            }
                            else
                            {
                                sc = GetDefaultValue( ulCurSet,
                                                      pUPropInfo->dwPropId, 
                                                      &(pCurProp->dwOptions),
                                                      &(pCurProp->vValue) );
    
                            }
    
                            if ( FAILED( sc ) )
                            {
                                pCurProp->vValue.vt = VT_EMPTY;
                                THROW( CException( sc ) );
                            }
    
                            dwStatus |= GETPROP_VALIDPROP;
                        }
                        else
                        {
                            // Not Supported
    
                            pCurProp->dwStatus = DBPROPSTATUS_NOTSUPPORTED;
                            dwStatus |= GETPROP_ERRORSOCCURRED;
                        }
                    }
    
                    // Increment return nodes count
    
                    ulNext++;
                }
    
                // Make sure we support the property set
    
                if ( 0 != ( dwStatus & GETPROP_NOTSUPPORTED ) )
                {
                    ulNext = cProps;
                    goto NEXT_SET;
                }
            }
            else
            {
                THROW( CException( E_OUTOFMEMORY ) );
            }
    
NEXT_SET:
            // It is possible that all properties are not supported,
            // thus we should delete that memory and set rgProperties
            // to NULL
    
            if ( 0 == ulNext && 0 != pProp )
            {
                CoTaskMemFree( pProp );
                pProp = 0;
            }
    
            xProp.Acquire();
    
            pPropSet[iPropSet].cProperties = ulNext;
            pPropSet[iPropSet].rgProperties = pProp;
            iPropSet++;
            Win4Assert( iPropSet <= cSets );
        }
    
        Win4Assert( iPropSet <= cSets );
    
        *pcProperties = iPropSet;
        *prgProperties = pPropSet;
        
        // At least one propid was marked as not S_OK
    
        if ( dwStatus & GETPROP_ERRORSOCCURRED )
        {
            // If at least 1 property was set
            if ( dwStatus & GETPROP_VALIDPROP )
                return DB_S_ERRORSOCCURRED;
            else
            {
                // Do not free any of the memory on a DB_E_
                return DB_E_ERRORSOCCURRED;
            }
        }
    
        sc = S_OK;
    }
    CATCH( CException, e )
    {
        // Since we have no properties to return we need to free allocated memory
    
        if ( 0 != pPropSet )
        {
            // Free any DBPROP arrays
    
            for( ulSet = 0; ulSet < cSets; ulSet++ )
            {
                // Need to loop through all the VARIANTS and clear them
    
                for( ulProp = 0; ulProp < pPropSet[ulSet].cProperties; ulProp++ )
                    VariantClear(&(pPropSet[ulSet].rgProperties[ulProp].vValue));
    
                CoTaskMemFree( pPropSet[ulSet].rgProperties );
            }
    
            // Free DBPROPSET
    
            CoTaskMemFree( pPropSet );
        }
    
        *pcProperties = 0;
        *prgProperties = 0;

        sc = e.GetErrorCode();
    }
    END_CATCH
    
    return sc;
} //CUtlProps::GetProperties

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProp::SetProperties, public
//
//  Synopsis:   This routine takes the array of properties that the consumer 
//              has give, determines whether each property can be supported.
//              For the ones supported, it sets a bit.  
// 
//  Arguments:  [cPropertySets]     - count of DBPROPSETS in rgPropertySets
//              [rgPropertySets]    - array of DBPROPSETS of values to be set
//
//  Returns:    SCODE indicating the following:
//              S_OK                - Success
//              E_FAIL              - Provider specific error
//              DB_S_ERRORSOCCURRED - One or more properties not set
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

SCODE CUtlProps::SetProperties
    (
    const ULONG             cPropertySets,      
    const DBPROPSET         rgPropertySets[]    
    )
{
    DWORD           dwState = 0;
    ULONG           ulSet, ulCurSet, ulCurProp, ulProp;
    DBPROP*         rgDBProp;
    UPROPINFO*      pUPropInfo;
    VARIANT         vDefaultValue;
    DWORD           dwOption;
    
    Win4Assert( ! cPropertySets || rgPropertySets );

    // Initialize Variant
    VariantInit(&vDefaultValue);

    // Process property sets
    for(ulSet=0; ulSet<cPropertySets; ulSet++)
    {
        Win4Assert( ! rgPropertySets[ulSet].cProperties || rgPropertySets[ulSet].rgProperties );

        // Make sure we support the property set
        if ( GetIndexofPropSet(&(rgPropertySets[ulSet].guidPropertySet),
            &ulCurSet) == S_FALSE )
        {
            // Not supported, thus we need to mark all as NOT_SUPPORTED
            rgDBProp = rgPropertySets[ulSet].rgProperties;
            for(ulProp=0; ulProp<rgPropertySets[ulSet].cProperties; ulProp++)
            {
                dwState |= SETPROP_ERRORS;
                rgDBProp[ulProp].dwStatus = DBPROPSTATUS_NOTSUPPORTED;
            }
            continue;
        }

        // Handle properties of a supported property set
        rgDBProp = rgPropertySets[ulSet].rgProperties;
        for(ulProp=0; ulProp<rgPropertySets[ulSet].cProperties; ulProp++)
        {
            // Is this a supported PROPID for this property set
            if ( GetIndexofPropIdinPropSet(ulCurSet, rgDBProp[ulProp].dwPropertyID, 
                &ulCurProp) == S_FALSE)
            {
                dwState |= SETPROP_ERRORS;
                rgDBProp[ulProp].dwStatus = DBPROPSTATUS_NOTSUPPORTED;
                continue;
            }

            // Set the pUPropInfo pointer
            pUPropInfo = (UPROPINFO*)&(_pUPropSet[ulCurSet].pUPropInfo[ulCurProp]); 
            Win4Assert( pUPropInfo );

            // check dwOption for a valid option
            if ( (rgDBProp[ulProp].dwOptions != DBPROPOPTIONS_REQUIRED)  &&
                (rgDBProp[ulProp].dwOptions != DBPROPOPTIONS_OPTIONAL) )
            {
                vqDebugOut(( DEB_WARN, "SetProperties dwOptions Invalid: %u\n", rgDBProp[ulProp].dwOptions ));
                dwState |= SETPROP_ERRORS;
                rgDBProp[ulProp].dwStatus = DBPROPSTATUS_BADOPTION;
                continue;
            }

            // Check that the property is settable
            // @devnote: We do not check against DBPROPFLAGS_CHANGE here
            if ( (pUPropInfo->dwFlags & DBPROPFLAGS_WRITE) == 0 )
            {
                rgDBProp[ulProp].dwStatus = DBPROPSTATUS_OK;

                VariantClear(&vDefaultValue);

                // VT_EMPTY against a read only property should be a no-op since
                // the VT_EMPTY means the default.
                if ( V_VT(&rgDBProp[ulProp].vValue) == VT_EMPTY )
                {
                    dwState |= SETPROP_VALIDPROP;
                    continue;
                }

                if ( SUCCEEDED(GetDefaultValue(ulCurSet, rgDBProp[ulProp].dwPropertyID, 
                        &dwOption, &(vDefaultValue))) )
                {
                    if ( V_VT(&rgDBProp[ulProp].vValue) ==  V_VT(&vDefaultValue) )
                    {
                        switch( V_VT(&vDefaultValue) )
                        {
                            case VT_BOOL:
                                if ( V_BOOL(&rgDBProp[ulProp].vValue) == V_BOOL(&vDefaultValue) )
                                {
                                    dwState |= SETPROP_VALIDPROP;
                                    continue;
                                }
                                break;
                            case VT_I2:
                                if ( V_I2(&rgDBProp[ulProp].vValue) == V_I2(&vDefaultValue) )
                                {
                                    dwState |= SETPROP_VALIDPROP;
                                    continue;
                                }
                                break;
                            case VT_I4:
                                if ( V_I4(&rgDBProp[ulProp].vValue) == V_I4(&vDefaultValue) )
                                {
                                    dwState |= SETPROP_VALIDPROP;
                                    continue;
                                }
                                break;
                            case VT_BSTR:
                                if ( wcscmp(V_BSTR(&rgDBProp[ulProp].vValue), V_BSTR(&vDefaultValue)) == 0 )
                                {
                                    dwState |= SETPROP_VALIDPROP;
                                    continue;
                                }
                                break;
                        }
                    }
                }

                dwState |= SETPROP_ERRORS;
                rgDBProp[ulProp].dwStatus = DBPROPSTATUS_NOTSETTABLE;
                continue;
            }

            // Check that the property is being set with the correct VARTYPE
            if ( (rgDBProp[ulProp].vValue.vt != pUPropInfo->VarType) && 
                (rgDBProp[ulProp].vValue.vt != VT_EMPTY) )
            {
                dwState |= SETPROP_ERRORS;
                rgDBProp[ulProp].dwStatus = DBPROPSTATUS_BADVALUE;
                continue;
            }

            // Check that the value is legal
            if ( (rgDBProp[ulProp].vValue.vt != VT_EMPTY) && 
                IsValidValue(ulCurSet, &(rgDBProp[ulProp])) == S_FALSE )
            {
                dwState |= SETPROP_ERRORS;
                rgDBProp[ulProp].dwStatus = DBPROPSTATUS_BADVALUE;
                continue;
            }


            if ( SUCCEEDED(SetProperty(ulCurSet, ulCurProp, &(rgDBProp[ulProp]))) )
            {
                dwState |= SETPROP_VALIDPROP;
            }
        }
    }

    VariantClear(&vDefaultValue);

    // At least one propid was marked as not S_OK
    if ( dwState & SETPROP_ERRORS )
    {
        // If at least 1 property was set
        if ( dwState & SETPROP_VALIDPROP )
            return DB_S_ERRORSOCCURRED;
        else
            return DB_E_ERRORSOCCURRED;
    }
    
    return S_OK;
} // CUtlProp::SetProperties

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProp::IsPropSet, public
//
//  Synopsis:   Check if a VT_BOOL property is true or false
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

BOOL CUtlProps::IsPropSet
    (
    const GUID*     pguidPropSet,
    DBPROPID        dwPropId
    )
{
    SCODE       sc = S_OK;
    ULONG       iPropSet;
    ULONG       iPropId;
    VARIANT     vValue;
    DWORD       dwOptions;

    VariantInit(&vValue);

    if ( GetIndexofPropSet(pguidPropSet, &iPropSet) == S_OK )
    {
        if ( GetIndexofPropIdinPropSet(iPropSet, dwPropId, &iPropId) == S_OK )
        {       
            if ( _pUPropSet[iPropSet].pUPropInfo[iPropId].dwFlags & 
                (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
            {
                ULONG iPropVal = GetUPropValIndex(iPropSet, dwPropId);
                
                dwOptions = _xaUProp[iPropSet].pUPropVal[iPropVal].dwOption;
                sc = VariantCopy( &vValue,
                                  &(_xaUProp[iPropSet].pUPropVal[iPropVal].vValue) );
            }
            else
            {
                sc = GetDefaultValue( iPropSet, dwPropId, 
                                      &dwOptions, &vValue );
            }

            if ( dwOptions == DBPROPOPTIONS_REQUIRED )
            {
                if ( SUCCEEDED(sc) )
                {
                    Win4Assert( vValue.vt == VT_BOOL );
                    if ( V_BOOL(&vValue) == VARIANT_TRUE )
                    {
                        VariantClear(&vValue);
                        return TRUE;
                    }
                }
            }
        }
    }

    VariantClear(&vValue);
    return FALSE;
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProp::GetPropValue, public
//
//  Synopsis:   
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

SCODE CUtlProps::GetPropValue
    (
    const GUID*     pguidPropSet, 
    DBPROPID        dwPropId, 
    VARIANT*        pvValue
    )
{
    SCODE       sc = E_FAIL;
    ULONG       iPropSet;
    ULONG       iPropId;
    DWORD       dwOptions;

    if ( GetIndexofPropSet(pguidPropSet, &iPropSet) == S_OK )
    {
        if ( GetIndexofPropIdinPropSet(iPropSet, dwPropId, &iPropId) == S_OK )
        {       
            if ( _pUPropSet[iPropSet].pUPropInfo[iPropId].dwFlags & (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
            {
                sc = VariantCopy( pvValue, 
                                  &(_xaUProp[iPropSet].pUPropVal[
                                    GetUPropValIndex(iPropSet, dwPropId)].vValue) );
            }
            else
            {
                VariantClear(pvValue);

                sc = GetDefaultValue( iPropSet, 
                                      dwPropId, 
                                      &dwOptions, 
                                      pvValue );
            }
        }
    }

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProp::SetPropValue, public
//
//  Synopsis:   
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

HRESULT CUtlProps::SetPropValue
    (
    const GUID*     pguidPropSet,
    DBPROPID        dwPropId, 
    VARIANT*        pvValue
    )
{
    SCODE       sc = E_FAIL;
    ULONG       iPropSet;
    ULONG       iPropId;

    if ( GetIndexofPropSet(pguidPropSet, &iPropSet) == S_OK )
    {
        if ( GetIndexofPropIdinPropSet(iPropSet, dwPropId, &iPropId) == S_OK )
        {       
            Win4Assert( _pUPropSet[iPropSet].pUPropInfo[iPropId].dwFlags & (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) );

            sc = VariantCopy( &(_xaUProp[iPropSet].pUPropVal[
                                    GetUPropValIndex(iPropSet, dwPropId)].vValue), 
                              pvValue );
        }
    }

    return sc;
}


//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::CopyUPropVal, public
//
//  Synopsis:   Copy the values stored in UpropVal
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

SCODE CUtlProps::CopyUPropVal
    (
    ULONG           iPropSet,
    UPROPVAL*       rgUPropVal
    )
{
    SCODE  sc = S_OK;
    DBPROP dbProp;

    Win4Assert( rgUPropVal );
    Win4Assert( iPropSet < _cUPropSet );
    
    VariantInit(&dbProp.vValue);

    UPROP * pUProp = &(_xaUProp[iPropSet]);

    for(ULONG ul=0; ul<pUProp->cPropIds; ul++)
    {
        UPROPVAL * pUPropVal = &(pUProp->pUPropVal[ul]);

        // Transfer dwOptions
        rgUPropVal[ul].dwOption = pUPropVal->dwOption;

        // Transfer Flags
        rgUPropVal[ul].dwFlags = pUPropVal->dwFlags;

        // Transfer value
        VariantInit(&(rgUPropVal[ul].vValue));
        sc = VariantCopy( &(rgUPropVal[ul].vValue), &(pUPropVal->vValue) );

        if ( FAILED(sc) )
            break;
    }

    VariantClear( &(dbProp.vValue) );
    return sc;
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::GetIndexofPropSet, public
//
//  Synopsis:   Given a propset guid, find the index of the propset in our 
//              current set to be returned.
//
//  Arguments:  [pPropset]      - Guid of propset to find index of
//              [pulCurSet]     - Index of current property set [out]
//
//  Returns:    SCODE indicating the following:
//              S_OK    - guid was matched and index returned
//              S_FALSE - guid was not matched
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

SCODE CUtlProps::GetIndexofPropSet
    (
    const GUID* pPropSet,   
    ULONG*  pulCurSet       
    )
{
    ULONG ul;

    Win4Assert( pPropSet && pulCurSet );

    for(ul=0; ul<_cUPropSet; ul++)
    {
        if ( *pPropSet == *(_pUPropSet[ul].pPropSet) )
        {
            *pulCurSet = ul;
            return S_OK;
        }
    }

    return S_FALSE;
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::GetIndexofPropIdinPropSet, public
//
//  Synopsis:   Given a propertyset guid, determine what index in our current
//              set of property sets is to be returned
//
//  Arguments:  [iCurSet]       - Index of current property set
//              [dwPropertyId]  - Property id
//              [piCurPropId]   - Index of requeted id [out]
//
//  Returns:    SCODE as follows:
//              S_OK    - Propid found in propset
//              S_FALSE - Propid not found in propset
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

SCODE CUtlProps::GetIndexofPropIdinPropSet
    (
    ULONG       iCurSet,        
    DBPROPID    dwPropertyId,   
    ULONG*      piCurPropId     
    )
{
    ULONG       ul;
    UPROPINFO*  pUPropInfo;

    Win4Assert( piCurPropId );

    pUPropInfo = (UPROPINFO*)_pUPropSet[iCurSet].pUPropInfo;

    for(ul=0; ul<_pUPropSet[iCurSet].cUPropInfo; ul++)
    {
        if ( dwPropertyId == pUPropInfo[ul].dwPropId )
        {
            *piCurPropId = ul;

            // Test to see if the property is supported for this
            // instantiation
            if ( TESTBIT(&(_xadwSupported[iCurSet * _cElemPerSupported]), ul) )
                return S_OK;
            else
                return S_FALSE;
        }
    }

    return S_FALSE;
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::FInit, protected
//
//  Synopsis:   Initialization.  Called from constructors of derived classes.
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

void CUtlProps::FInit( CUtlProps * pCopyMe )
{
    SCODE               sc = S_OK;
    ULONG               ulPropId;
    ULONG               cPropIds;
    ULONG               iPropSet;
    ULONG               iNewDex;
    XArray<UPROPINFO*>  xapUPropInfo;
    XArray<UPROPVAL>    xaUPropVal;
    UPROPINFO *         pUPropInfo;
    
    // If a pointer is passed in, we should copy that property object
    if ( pCopyMe )
    {
        // Establish some base values
        pCopyMe->CopyAvailUPropSets( &_cUPropSet, 
                                     &_pUPropSet,
                                     &_cElemPerSupported );

        Win4Assert( (_cUPropSet != 0)  && (_cElemPerSupported != 0) );
        
        // Retrieve Supported Bitmask
        _xadwPropsInError.Init( _cUPropSet * _cElemPerSupported );
        ClearPropertyInError();
        
        // This uses XArray<>::Init to allocate and copy into _xadwSupported
        pCopyMe->CopyUPropSetsSupported( _xadwSupported );
    }
    else
    {
        sc = InitAvailUPropSets( &_cUPropSet, &_pUPropSet, &_cElemPerSupported );
        if ( FAILED(sc) )
            THROW( CException(sc) );

        Win4Assert( (_cUPropSet != 0)  && (_cElemPerSupported != 0) );
        if ( !_cUPropSet || !_cElemPerSupported )
            THROW( CException(E_FAIL) );

        _xadwSupported.Init( _cUPropSet * _cElemPerSupported );
        _xadwPropsInError.Init( _cUPropSet * _cElemPerSupported );

        ClearPropertyInError();
        
        // Set all slots to an initial value
        sc = InitUPropSetsSupported( _xadwSupported.GetPointer() ); 
        if ( FAILED(sc) )
        {
            _xadwSupported.Free();
            THROW( CException(sc) );
        }
    }

    // Allocate UPROPS structures for the count of Property sets
    _xaUProp.Init( _cUPropSet );

    RtlZeroMemory( _xaUProp.GetPointer(), _cUPropSet * sizeof(UPROP) );

    // Within the UPROPS Structure allocate and intialize the
    // Property IDs that belong to this property set.
    for(iPropSet=0; iPropSet<_cUPropSet; iPropSet++)
    {
        cPropIds = GetCountofWritablePropsInPropSet( iPropSet );

        if ( cPropIds > 0 )
        {
            // Initialize/allocate xaUPropVal.  Even when we are copying, the
            // copy routine simply does a member-wise value assignment.

            xaUPropVal.Init( cPropIds );

            if ( pCopyMe )
            {
                // CopyUPropInfo uses XArray<>::Init to allocate/copy xapUPropInfo

                pCopyMe->CopyUPropInfo( iPropSet, xapUPropInfo );

                // CopyUPropVal only transfers values.  Allocate xaUPropVal before calling this
                Win4Assert( !xaUPropVal.IsNull() );
                sc = pCopyMe->CopyUPropVal( iPropSet, xaUPropVal.GetPointer() );
                if ( FAILED(sc) )
                    THROW( CException(sc) );
            }
            else
            {
                // Initialize and allocate arrays
                xapUPropInfo.Init( cPropIds );

                // Clear Pointer Array
                RtlZeroMemory( xapUPropInfo.GetPointer(), cPropIds * sizeof(UPROPINFO *) );

                // Set Pointer to correct property ids with a property set
                pUPropInfo = (UPROPINFO *) _pUPropSet[iPropSet].pUPropInfo;

                // Set up the writable property buffers
                iNewDex = 0;
                for(ulPropId=0; ulPropId<_pUPropSet[iPropSet].cUPropInfo; ulPropId++)
                {
                    if ( pUPropInfo[ulPropId].dwFlags & (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
                    {
                        // Following assert indicates that the are more
                        // writable properties then space allocated
                        Win4Assert( iNewDex < cPropIds );

                        xapUPropInfo[iNewDex] = &(pUPropInfo[ulPropId]);
                        xaUPropVal[iNewDex].dwOption = DBPROPOPTIONS_OPTIONAL;
                        xaUPropVal[iNewDex].dwFlags = 0;
                        VariantInit(&(xaUPropVal[iNewDex].vValue));

                        sc = GetDefaultValue( iPropSet, 
                                              pUPropInfo[ulPropId].dwPropId, 
                                              &(xaUPropVal[iNewDex].dwOption), 
                                              &(xaUPropVal[iNewDex].vValue) );

                        if ( FAILED( sc ) )
                            THROW( CException(sc) );

                        iNewDex++;
                    }
                }
                
                Win4Assert( cPropIds == iNewDex );
            }

            _xaUProp[iPropSet].rgpUPropInfo = xapUPropInfo.Acquire();
            _xaUProp[iPropSet].pUPropVal = xaUPropVal.Acquire();
            _xaUProp[iPropSet].cPropIds = cPropIds;
        }
    }

    // Finally determine if there are any hidden property sets..  Those
    // that do not show up in GetPropertyInfo and should not be returns on 
    // a 0, NULL call to GetProperties
    for(iPropSet=0; iPropSet<_cUPropSet; iPropSet++)
    {
        if ( _pUPropSet[iPropSet].dwFlags & UPROPSET_HIDDEN )
            _cUPropSetHidden++;
    }
} // CUtlProps::FInit


//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::FreeMemory, private
//
//  Synopsis:   Free all memory
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

void CUtlProps::FreeMemory()
{
    ULONG       ulPropSet;
    ULONG       ulPropId;
    UPROPVAL*   pUPropVal;

    // Remove Property Information
    if ( !_xaUProp.IsNull() )
    {
        //
        // Reset _cUPropSet to its max. Used by derived classes that expose a subset
        // of their prop sets in some states. (eg. CMDSProps hides all but DBPROPSET_INIT
        // if the datasource is in an uninitlaized state)
        //
        for(ulPropSet=0; ulPropSet<_cUPropSet; ulPropSet++)
        {
            pUPropVal = _xaUProp[ulPropSet].pUPropVal;
            for(ulPropId=0; ulPropId<_xaUProp[ulPropSet].cPropIds; ulPropId++)
            {
                VariantClear(&(pUPropVal[ulPropId].vValue));
            }
            // TODO: UPROPSET change structure to use XArray
            delete[] _xaUProp[ulPropSet].rgpUPropInfo;
            delete[] _xaUProp[ulPropSet].pUPropVal;
        }
    
        _xaUProp.Free();
    }
    
    _xadwSupported.Free();
    _xadwPropsInError.Free();
    _xaiPropSetDex.Free();

    _cPropSetDex        = 0;
    _cUPropSet          = 0;
    _cUPropSetHidden    = 0;
    _dwFlags            = 0;
    _cElemPerSupported  = 0;
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::GetCountofWritablePropsInPropSet, private
//
//  Synopsis:   Given an index to the property set, count the number of
//              writable or changable properties under this property set.
//              devnote: this includes properties with the internal flag of
//                       DBPROPFLAGS_CHANGE along with DBPROPFLAGS_WRITE
//
//  Returns:    Count of writable properties
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

ULONG CUtlProps::GetCountofWritablePropsInPropSet
    (
    ULONG   iPropSet            //@parm IN | Index of Property Set
    )
{
    ULONG       ul;
    ULONG       cWritable = 0;
    UPROPINFO*  pUPropInfo;

    Win4Assert( _pUPropSet );
    Win4Assert( iPropSet < _cUPropSet );

    pUPropInfo = (UPROPINFO*)_pUPropSet[iPropSet].pUPropInfo;
    
    for(ul=0; ul<_pUPropSet[iPropSet].cUPropInfo; ul++)
    {
        if ( pUPropInfo[ul].dwFlags & (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
            cWritable++;
    }
    
    return cWritable;
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::GetUPropValIndex, private
//
//  Synopsis:   
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

ULONG CUtlProps::GetUPropValIndex
    (
    ULONG       iCurSet,
    DBPROPID    dwPropId
    )
{
    ULONG ul;

    for(ul=0; ul<_xaUProp[iCurSet].cPropIds; ul++)
    {
        if ( (_xaUProp[iCurSet].rgpUPropInfo[ul])->dwPropId == dwPropId )
            return ul;
    }

    return 0;
}


//+---------------------------------------------------------------------------
//
//  Method:     CUtlProps::SetProperty, private
//
//  Synopsis:   The iCurProp is an index into _pUPropSet, not into _xaUProp
//
//  Arguments:  [iCurSet]   - Index within _xaUProp and _pUPropSet
//              [iCurProp]  -
//              [pDBProp]   - Pointer to current property node [out]
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

SCODE CUtlProps::SetProperty
    (
    ULONG       iCurSet,        //@parm IN | Index within _xaUProp and _pUPropSet
    ULONG       iCurProp,
    DBPROP*     pDBProp         //@PARM INOUT | Pointer to current property node
    )
{
    SCODE       sc = S_OK;
    UPROP*      pUProp;
    UPROPVAL*   pUPropVal;
    UPROPINFO*  pUPropInfo;
    ULONG       iUProp;

    Win4Assert( pDBProp );

    // Set pointer to correct set
    pUProp = &(_xaUProp[iCurSet]);
    Win4Assert( pUProp );

    pUPropInfo = (UPROPINFO*)&(_pUPropSet[iCurSet].pUPropInfo[iCurProp]);
    Win4Assert( pUPropInfo );

    // Determine the index within _xaUProp  
    for(iUProp=0; iUProp<pUProp->cPropIds; iUProp++)
    {
        if ( (pUProp->rgpUPropInfo[iUProp])->dwPropId == pDBProp->dwPropertyID )
            break; 
    }

    if ( iUProp >= pUProp->cPropIds )
    {
        Win4Assert( !"Should have found index of property to set" );
        sc = E_FAIL;
        pDBProp->dwStatus = DBPROPSTATUS_NOTSUPPORTED;
        goto EXIT;
    }

    //Get the UPROPVAL node pointer within that propset.
    pUPropVal = &(pUProp->pUPropVal[iUProp]);
    Win4Assert( pUPropVal );

    // Handle VT_EMPTY, which indicates to the provider to 
    // reset this property to the providers default
    if ( pDBProp->vValue.vt == VT_EMPTY )
    {
        // Should clear here, since previous values may already
        // have been cached and need to be replaced.
        VariantClear(&(pUPropVal->vValue));

        pUPropVal->dwFlags &= ~DBINTERNFLAGS_CHANGED;
        sc = GetDefaultValue( iCurSet, 
                              pDBProp->dwPropertyID, 
                              &(pUPropVal->dwOption), 
                              &(pUPropVal->vValue) );

        goto EXIT;
    }
        
    pUPropVal->dwOption = pDBProp->dwOptions;
    sc = VariantCopy( &(pUPropVal->vValue), &(pDBProp->vValue) );
    if ( FAILED(sc)  )
        goto EXIT;
    pUPropVal->dwFlags |= DBINTERNFLAGS_CHANGED;

EXIT:
    if ( SUCCEEDED(sc) )
        pDBProp->dwStatus = DBPROPSTATUS_OK;

    return sc;
}


//
//  CUtlPropInfo methods
//

//+---------------------------------------------------------------------------
//
//  Method:     CUtlPropInfo::CUtlPropInfo, public
//
//  Synopsis:   Constructor
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

CUtlPropInfo::CUtlPropInfo() :
    _cUPropSet(0),
    _cPropSetDex(0),
    _cElemPerSupported(0),
    _pUPropSet(0)
{
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlPropInfo::GetPropertyInfo, public
//
//  Synopsis:   Retrieve the requested property info
//
//  Arguments:  [cPropertySets]       - Count of property sets
//              [rgPropertySets]      - Property sets
//              [pcPropertyInfoSets]  - Count of properties returned [out]
//              [prgPropertyInfoSets] - Property information returned [out]
//              [ppDescBuffer]        - Buffer for returned descriptions [out]
//
//  Returns:    SCODE as follows:
//              S_OK    - At least one index returned
//              S_FALSE - No matching property set found
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

SCODE CUtlPropInfo::GetPropertyInfo
    (
    ULONG               cPropertySets,      
    const DBPROPIDSET   rgPropertySets[],   
    ULONG*              pcPropertyInfoSets, 
    DBPROPINFOSET**     prgPropertyInfoSets,
    WCHAR**             ppDescBuffer        
    )
{
    SCODE           sc = S_OK;
    ULONG           ul, ulSet, ulNext, ulEnd, ulProp;
    ULONG           ulOutIndex;
    ULONG           cSets;
    ULONG           cPropInfos;
    DWORD           dwStatus = 0;
    DBPROPINFO*     pCurPropInfo = 0;
    WCHAR*          pDescBuffer = 0;
    UPROPINFO*      pUPropInfo = 0;
    WCHAR           wszBuff[256];
    int             cch;


    XArrayOLE<DBPROPINFO>   xaPropInfo;
    XArrayOLE<WCHAR>        xawszDescBuffer;

    // If the consumer does not restrict the property sets
    // by specify an array of property sets and a cPropertySets
    // greater than 0, then we need to make sure we 
    // have some to return
    if ( cPropertySets == 0 )
    {
        // Determine the number of property sets supported
        cSets = _cUPropSet;
    }
    else
    {
        cSets = 0;

        // Determine number of property sets required 
        // This is only required when any of the "special" property set GUIDs were specified
        for(ulSet=0; ulSet<cPropertySets; ulSet++)
        {
            // Note that we always allocate nodes, mark all bad properties,
            // and return DB_E_ERRORSOCCURRED.  See Nile spec bug 2665.
            // We always allocate at least one.
            if ( GetPropertySetIndex(&(rgPropertySets[ulSet].guidPropertySet)) == S_OK )
                cSets += _cPropSetDex;
            else
                cSets++;
        }
    }
    Win4Assert( cSets );

    //
    // Allocate the DBPROPINFOSET structures.  XArray zeors out the memory.
    //
    XArrayOLE<DBPROPINFOSET>    xaPropInfoSet( cSets );

    ulOutIndex = 0;
    ulEnd = cPropertySets == 0 ? cSets : cPropertySets; 

    // Fill in the output array
    for(ulSet=0; ulSet<ulEnd; ulSet++)
    {
        // Depending of if Property sets are specified store the
        // return property set.
        if (cPropertySets == 0)
        {
            xaPropInfoSet[ulSet].guidPropertySet = *(_pUPropSet[ulSet].pPropSet);
        }
        else
        {
            if ( ((rgPropertySets[ulSet].guidPropertySet == DBPROPSET_DATASOURCEALL) ||
                 (rgPropertySets[ulSet].guidPropertySet == DBPROPSET_DATASOURCEINFOALL) ||
                 (rgPropertySets[ulSet].guidPropertySet == DBPROPSET_DBINITALL) ||
                 (rgPropertySets[ulSet].guidPropertySet == DBPROPSET_SESSIONALL) ||
                 (rgPropertySets[ulSet].guidPropertySet == DBPROPSET_ROWSETALL)) &&
                (GetPropertySetIndex(&(rgPropertySets[ulSet].guidPropertySet)) == S_OK) )
            {
                for(ul=0; ul<_cPropSetDex; ul++,ulOutIndex++)
                {
                    xaPropInfoSet[ulOutIndex].guidPropertySet   = *(_pUPropSet[_xaiPropSetDex[ul]].pPropSet);
                    xaPropInfoSet[ulOutIndex].cPropertyInfos    = 0;
                }
            }
            else
            {
                // Handle non-category property sets
                // Handle unknown property sets
                xaPropInfoSet[ulOutIndex].guidPropertySet = rgPropertySets[ulSet].guidPropertySet; 
                xaPropInfoSet[ulOutIndex].cPropertyInfos  = rgPropertySets[ulSet].cPropertyIDs; 
                ulOutIndex++;
            }
        }
    }
        
    // Allocate a Description Buffer if needed
    if ( ppDescBuffer )
    {
        ULONG cBuffers = CalcDescripBuffers(cSets, xaPropInfoSet.GetPointer());
        if ( 0 != cBuffers )
        {
            xawszDescBuffer.Init( cBuffers * CCH_GETPROPERTYINFO_DESCRIP_BUFFER_SIZE );
            pDescBuffer = xawszDescBuffer.GetPointer();
        }
        else
        {
            // No buffers needed.  Make sure client already set to NULL.
            Win4Assert( 0 == *ppDescBuffer );
        }
    }

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        // Process requested or derived Property sets
        dwStatus = 0;
        for(ulSet=0; ulSet<cSets; ulSet++)
        {
            ulNext=0;
            cPropInfos = 0;

            dwStatus &= (GETPROPINFO_ERRORSOCCURRED | GETPROPINFO_VALIDPROP);

            // Calculate the number of property nodes needed for this
            // property set.
            if ( cPropertySets == 0 )
            {
                cPropInfos = _pUPropSet[ulSet].cUPropInfo;
                dwStatus |= GETPROPINFO_ALLPROPIDS;
                _xaiPropSetDex[0] = ulSet;
                _cPropSetDex = 1;
            }
            else
            {
                // If the count of PROPIDs is 0 (NOTE: the above routine already determined
                // if it belonged to a category and if so set the count of properties to 0 for
                // each propset in that category.
                if ( 0 == xaPropInfoSet[ulSet].cPropertyInfos )
                {
                    dwStatus |= GETPROPINFO_ALLPROPIDS;
                    // We have to determine if the property set is supported and if so
                    // the count of properties in the set.
                    if ( GetPropertySetIndex(&(xaPropInfoSet[ulSet].guidPropertySet)) == S_OK )             
                    {
                        Win4Assert( _cPropSetDex == 1 );
                        
                        cPropInfos += _pUPropSet[_xaiPropSetDex[0]].cUPropInfo;
                    }
                    else
                    {
                        // Not Supported                    
                        dwStatus |= GETPROPINFO_ERRORSOCCURRED;
                        goto NEXT_SET;
                    }
                }
                else
                {
                    cPropInfos = xaPropInfoSet[ulSet].cPropertyInfos;
                    if ( GetPropertySetIndex(&(xaPropInfoSet[ulSet].guidPropertySet)) == S_FALSE )
                    {
                        dwStatus |= GETPROPINFO_NOTSUPPORTED;
                        dwStatus |= GETPROPINFO_ERRORSOCCURRED;
                    }
                }
            }


            // Allocate DBPROP array
            Win4Assert( cPropInfos != 0 );
        
            xaPropInfo.Init( cPropInfos ); 

            for(ulProp=0; ulProp<cPropInfos; ulProp++)
            {
                VariantInit(&(xaPropInfo[ulProp].vValues));
                if ( dwStatus & GETPROPINFO_NOTSUPPORTED )
                {
                    // Not supported, thus we need to mark all as NOT_SUPPORTED
                    xaPropInfo[ulProp].dwPropertyID = rgPropertySets[ulSet].rgPropertyIDs[ulProp];
                    xaPropInfo[ulProp].dwFlags = DBPROPFLAGS_NOTSUPPORTED;
                    dwStatus |= GETPROPINFO_ERRORSOCCURRED;
                }                   
            }
            // Make sure we support the property set
            if ( dwStatus & GETPROPINFO_NOTSUPPORTED )
            {
                ulNext = cPropInfos;
                goto NEXT_SET;
            }

            // Retrieve the property information for this property set
            for(ul=0; ul<_cPropSetDex; ul++)
            {
                pUPropInfo = (UPROPINFO*)(_pUPropSet[_xaiPropSetDex[ul]].pUPropInfo);
                Win4Assert( pUPropInfo );
                            
                // Retrieve current value of properties
                if ( dwStatus & GETPROPINFO_ALLPROPIDS )
                {
                    for(ulProp=0; ulProp<_pUPropSet[_xaiPropSetDex[ul]].cUPropInfo; ulProp++)
                    {
                        // Verify property is supported, if not do not return 
                        if ( !TESTBIT(&(_xadwSupported[_xaiPropSetDex[ul] * _cElemPerSupported]), ulProp) )
                            continue;

                        pCurPropInfo = &(xaPropInfo[ulNext]);

                        // If the ppDescBuffer pointer was not NULL, then
                        // we need supply description of the properties
                        if ( ppDescBuffer )
                        {
                            // Set Buffer pointer
                            pCurPropInfo->pwszDescription = pDescBuffer;
                            // Load the string into temp buffer
    //@TODO Add Reallocation routine
                            cch = LoadDescription( pUPropInfo[ulProp].pwszDesc, 
                                                   wszBuff, 
                                                   NUMELEM(wszBuff) );
                            if ( 0 != cch )
                            {
                                // Adjust for '\0'
                                cch++;

                                // Transfer to official buffer if room
                                RtlCopyMemory( pDescBuffer, wszBuff, cch * sizeof(WCHAR) );
                                pDescBuffer += cch;
                            }
                            else
                            {
                                wcscpy( pDescBuffer, L"UNKNOWN" );
                                pDescBuffer += NUMELEM(L"UNKNOWN");
                            }
                        }

                        pCurPropInfo->dwPropertyID  = pUPropInfo[ulProp].dwPropId;
                        pCurPropInfo->dwFlags       = pUPropInfo[ulProp].dwFlags;
                        pCurPropInfo->vtType        = pUPropInfo[ulProp].VarType;
    //@TODO for some there will be a list
                        pCurPropInfo->vValues.vt    = VT_EMPTY;

                        dwStatus |= GETPROPINFO_VALIDPROP;
                        // Increment to next available buffer
                        ulNext++;
                    }
                }
                else
                {
                    Win4Assert( _cPropSetDex == 1 );

                    for( ulProp = 0; ulProp < cPropInfos; ulProp++, ulNext++ )
                    {
                        pCurPropInfo = &(xaPropInfo[ulNext]);

                        // Process Properties based on Restriction array.
                        pCurPropInfo->dwPropertyID = rgPropertySets[ulSet].rgPropertyIDs[ulProp];
                    
                        if ( S_OK == GetUPropInfoPtr( _xaiPropSetDex[ul], 
                                                      pCurPropInfo->dwPropertyID, 
                                                      &pUPropInfo) )
                        {
                            // If the ppDescBuffer pointer was not NULL, then
                            // we need supply description of the properties
                            if ( ppDescBuffer )
                            {
                                // Set Buffer pointer
                                pCurPropInfo->pwszDescription = pDescBuffer;

                                // Load the string into temp buffer
                                if ( cch = LoadDescription( pUPropInfo->pwszDesc, 
                                                            wszBuff, 
                                                            NUMELEM(wszBuff)) )
                                {
                                    // Adjust for '\0'
                                    cch++;

                                    // Transfer to official buffer if room
                                    RtlCopyMemory( pDescBuffer, wszBuff, cch * sizeof(WCHAR) );
                                    pDescBuffer += cch;
                                }
                                else
                                {
                                    wcscpy(pDescBuffer, L"UNKNOWN");
                                    pDescBuffer += (wcslen(L"UNKNOWN") + 1);
                                }
                            }

                            pCurPropInfo->dwPropertyID  = pUPropInfo->dwPropId;
                            pCurPropInfo->dwFlags       = pUPropInfo->dwFlags;
                            pCurPropInfo->vtType        = pUPropInfo->VarType;

                            dwStatus |= GETPROPINFO_VALIDPROP;
                        }
                        else
                        {
                            // Not Supported
                            pCurPropInfo->dwFlags = DBPROPFLAGS_NOTSUPPORTED;
                            dwStatus |= GETPROPINFO_ERRORSOCCURRED;
                        }
                    }
                }
            }

NEXT_SET:
            xaPropInfoSet[ulSet].cPropertyInfos = ulNext;
            xaPropInfoSet[ulSet].rgPropertyInfos = xaPropInfo.Acquire();
        }

        // Success, set return values
        *pcPropertyInfoSets  = cSets;
        *prgPropertyInfoSets = xaPropInfoSet.Acquire();

        if ( ppDescBuffer )
            *ppDescBuffer = xawszDescBuffer.Acquire();

        // At least one propid was marked as not S_OK
        if ( dwStatus & GETPROPINFO_ERRORSOCCURRED )
        {
            // If at least 1 property was set
            if ( dwStatus & GETPROPINFO_VALIDPROP )
                return DB_S_ERRORSOCCURRED;
            else
            {
                // Do not free any of the rgPropertyInfoSets, but
                // do free the ppDescBuffer
                if ( pDescBuffer )
                {
                    Win4Assert( ppDescBuffer );
                    CoTaskMemFree( pDescBuffer );
                    *ppDescBuffer = 0;
                }
                return DB_E_ERRORSOCCURRED;
            }
        }

    }
    CATCH( CException, e )
    {
        // most likely E_OUTOFMEMORY
        sc = e.GetErrorCode();

        // Check if failure and clean up any allocated memory
        if ( FAILED(sc) ) 
        {
            // Free Description Buffer
            if ( pDescBuffer )
            {
                Win4Assert( ppDescBuffer );
                CoTaskMemFree( *ppDescBuffer );
                *ppDescBuffer = 0;
            }

            if ( !xaPropInfoSet.IsNull() )
            {
                // Loop through Property Sets
                for(ulSet=0; ulSet<cSets; ulSet++)
                {
                    if ( xaPropInfoSet[ulSet].rgPropertyInfos )
                        CoTaskMemFree( xaPropInfoSet[ulSet].rgPropertyInfos );
                }
            }
        }
        
        // Rethrow exception to the calling function
        RETHROW();
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    
    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlPropInfo::FInit, protected
//
//  Synopsis:   Initialization.  Called from constructors of derived classes.
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

void CUtlPropInfo::FInit
    (
    )
{
    SCODE sc = S_OK;

    InitAvailUPropSets( &_cUPropSet, 
                        &_pUPropSet, 
                        &_cElemPerSupported );

    Win4Assert( (_cUPropSet != 0)  && (_cElemPerSupported != 0) );
    if ( !_cUPropSet || !_cElemPerSupported )
        THROW( CException(E_FAIL) );

    _xadwSupported.Init( _cUPropSet * _cElemPerSupported );

    sc = InitUPropSetsSupported( _xadwSupported.GetPointer() );
    if ( FAILED(sc) )
    {
        _xadwSupported.Free();
        THROW( CException(sc) );
    }

    if ( _cUPropSet )
    {
        _xaiPropSetDex.Init( _cUPropSet );
    }

    return;
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlPropInfo::GetUPropInfoPtr, private
//
//  Synopsis:   Retrieve a pointer to the UPROPINFO structure that contains
//              information about this property id within this property set
//
//  Arguments:  [iPropSetDex]   - Index into UPropSets
//              [dwPropertyId]  - Property to search for with UPropSet
//              [ppUPropInfo]   - Pointer in which to return ptr to UPropInfo [out]
//
//  Returns:    SCODE as follows:
//              S_OK    - Property id found
//              S_FALSE - No matching prop id found or property not supported
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

SCODE CUtlPropInfo::GetUPropInfoPtr
    (
    ULONG       iPropSetDex,        //@parm IN | Index into UPropSets
    DBPROPID    dwPropertyId,       //@parm IN | Propery to search for with UPropSet
    UPROPINFO** ppUPropInfo         //@parm OUT | Pointer in which to return ptr to UPropInfo
    )
{
    ULONG ulProps;

    // Scan through the property sets looking for matching attributes
    for(ulProps=0; ulProps<_pUPropSet[iPropSetDex].cUPropInfo; ulProps++)
    {
        if ( _pUPropSet[iPropSetDex].pUPropInfo[ulProps].dwPropId == dwPropertyId )
        {
            *ppUPropInfo = (UPROPINFO*)&(_pUPropSet[iPropSetDex].pUPropInfo[ulProps]);

            // Test to see if the property is supported for this instantiation
            if ( TESTBIT(&(_xadwSupported[iPropSetDex * _cElemPerSupported]), ulProps) )
                return S_OK;
            else
                return S_FALSE;
        }
    }

    return S_FALSE;
}

//+---------------------------------------------------------------------------
//
//  Method:     CUtlPropInfo::GetPropertySetIndex, private
//
//  Synopsis:   Retrieve the index or indices of PropertySets that match the
//              given guid.
//
//              NOTE: If given a DBPROPET_DATASOURCEALL, DBPROPSET_DATASOURCEINFOALL
//                    or DBPROPSET_ROWSETALL, the routine may return multiple 
//                    indices.
//  Returns:    SCODE as follows:
//              S_OK    - At least one index was returned.
//              S_FALSE - No matching property set found
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

SCODE CUtlPropInfo::GetPropertySetIndex
    (
    const GUID* pPropertySet        //@parm IN | Pointer to Property Set Guid
    )
{
    DWORD   dwFlag = 0;
    ULONG   ulSet;

    Win4Assert( _cUPropSet && _pUPropSet );
    Win4Assert( !_xaiPropSetDex.IsNull() );
    Win4Assert( pPropertySet );

    _cPropSetDex = 0;

    if ( *pPropertySet == DBPROPSET_DATASOURCEALL )
    {
        dwFlag = DBPROPFLAGS_DATASOURCE;
    }
    else if ( *pPropertySet == DBPROPSET_DATASOURCEINFOALL )
    {
        dwFlag = DBPROPFLAGS_DATASOURCEINFO;
    }
    else if ( *pPropertySet == DBPROPSET_ROWSETALL )
    {
        dwFlag = DBPROPFLAGS_ROWSET;
    }
    else if ( *pPropertySet == DBPROPSET_DBINITALL )
    {
        dwFlag = DBPROPFLAGS_DBINIT;
    }
    else if ( *pPropertySet == DBPROPSET_SESSIONALL )
    {
        dwFlag = DBPROPFLAGS_SESSION;
    }
    else // No scan required, just look for match.
    {
        for(ulSet=0; ulSet<_cUPropSet; ulSet++)
        {
            if ( *(_pUPropSet[ulSet].pPropSet) == *pPropertySet )
            {
                _xaiPropSetDex[_cPropSetDex] = ulSet;
                _cPropSetDex++;
                break;
            }
        }
        goto EXIT;
    }

    // Scan through the property sets looking for matching attributes
    for(ulSet=0; ulSet<_cUPropSet; ulSet++)
    {
        if ( _pUPropSet[ulSet].pUPropInfo[0].dwFlags & dwFlag )
        {
            _xaiPropSetDex[_cPropSetDex] = ulSet;
            _cPropSetDex++;
        }
    }

EXIT:
    return (_cPropSetDex) ? S_OK : S_FALSE;
}


//+---------------------------------------------------------------------------
//
//  Method:     CUtlPropInfo::CalcDescripBuffers, private
//
//  Synopsis:   Calculate the number of description buffers that will be needed
//
//  Argunemts:  [cPropInfoSet]  - Count of propinfo sets
//              [pPropInfoSet]  - Property info sets
//
//  Returns:    Number of buffers needed
//
//  History:    11-12-97    danleg      Created from Monarch
//
//----------------------------------------------------------------------------

ULONG CUtlPropInfo::CalcDescripBuffers
    (
    ULONG           cPropInfoSet,       //@parm IN | count of property info sets
    DBPROPINFOSET*  pPropInfoSet        //@parm IN | property info sets
    )
{
    ULONG   ul, ulSet;
    ULONG   cBuffers = 0;

    Win4Assert( _pUPropSet && cPropInfoSet && pPropInfoSet );

    for(ulSet=0; ulSet<cPropInfoSet; ulSet++)
    {
        if ( GetPropertySetIndex(&(pPropInfoSet[ulSet].guidPropertySet)) == S_OK)
        {
            for(ul=0; ul<_cPropSetDex; ul++)
            {
                cBuffers += _pUPropSet[_xaiPropSetDex[ul]].cUPropInfo;
            }
        }
    }

    return cBuffers;
}