//
// IConnectionPoint/IDispatch helper functions
//
#include "priv.h"
#include <shlobj.h>

//
//  IDispatch helper functions

//
//  Takes a variable number of parameters for IDispatch, packages
//  them up.
//
//  pdispparams  - The DISPPARAMS structure that receives the result
//                 of the packaging.
//
//  rgvarg       - Array of length cArgs.
//                 It will be used to hold the parameters.
//
//  cArgs        - Number of pairs of generic arguments.
//
//  ap           - va_list of parameters to package.  We package up the
//                 first (2 * cArgs) of them.  See SHPackDispParams
//                 for details.

typedef struct FAKEBSTR {
    ULONG cb;
    WCHAR wsz[1];
} FAKEBSTR;

const FAKEBSTR c_bstrNULL = { 0, L"" };

LWSTDAPI SHPackDispParamsV(DISPPARAMS *pdispparams, VARIANTARG *rgvarg, UINT cArgs, va_list ap)
{
    HRESULT hr = S_OK;

    ZeroMemory(rgvarg, cArgs * SIZEOF(VARIANTARG));

    // fill out DISPPARAMS structure
    pdispparams->rgvarg = rgvarg;
    pdispparams->rgdispidNamedArgs = NULL;
    pdispparams->cArgs = cArgs;
    pdispparams->cNamedArgs = 0;

    // parameters are ordered in ap with the right-most parameter
    // at index zero and the left-most parameter at the highest index; essentially,
    // the parameters are pushed from right to left.  Put the first argument we
    // encounter at the highest index.

    // pVarArg points to the argument structure in the array we are currently
    // filling in.  Initialize this to point at highest argument (zero-based,
    // hence the -1).  For each passed-in argument we process, *decrement*
    // the pVarArg pointer to achieve the "push from right-to-left" effect.
    VARIANTARG * pVarArg = &rgvarg[cArgs - 1];

    int nCount = cArgs;
    while (nCount) 
    {
        VARENUM vaType = va_arg(ap,VARENUM);

        // We don't have to call VariantInit because we zerod out
        // the entire array before entering this loop

        V_VT(pVarArg) = vaType;

        // the next field is a union, so we can be smart about filling it in
        //
        if (vaType & VT_BYREF)
        {
            // All byrefs can be packed the same way
            V_BYREF(pVarArg) = va_arg(ap, LPVOID);
        }
        else
        {
            switch (vaType)
            {
            case VT_BSTR:
            {
                // parameter is a BSTR
                // MFC doesn't like it when you pass NULL for a VT_BSTR type
                V_BSTR(pVarArg) = va_arg(ap, BSTR);
                if (V_BSTR(pVarArg) == NULL)
                    V_BSTR(pVarArg) =(BSTR)c_bstrNULL.wsz;
#ifdef DEBUG
                // Check if this BSTR is a valid BSTR
                FAKEBSTR *bstr = CONTAINING_RECORD(V_BSTR(pVarArg), FAKEBSTR, wsz);
                ASSERT(bstr->cb == lstrlenW(bstr->wsz) * SIZEOF(WCHAR));
#endif
                break;
            }
    
            case VT_BOOL:
                V_BOOL(pVarArg) = va_arg(ap, VARIANT_BOOL);
                break;

            case VT_DISPATCH:
                V_DISPATCH(pVarArg) = va_arg(ap, LPDISPATCH);
                break;

            case VT_UNKNOWN:
                V_UNKNOWN(pVarArg) = va_arg(ap, LPUNKNOWN);
                break;

            default:
                AssertMsg(0, TEXT("Packing unknown variant type 0x%x as VT_I4"), vaType);
                // if we don't know what it is treat it as VT_I4.
                // Hopefully it's not a pointer or a VT_R8 or that sort of
                // thing, or we're in trouble.
                V_VT(pVarArg) = VT_I4;

            case VT_I4:
                V_I4(pVarArg) = va_arg(ap, LONG);
                break;

            } 
        }

        nCount--;
        pVarArg--;
    }

    return hr;
}

//
//  Takes a variable number of generic parameters, packages
//  them up.
//
//  pdispparams  - The DISPPARAMS structure that receives the result
//                 of the packaging.
//
//  rgvarg       - Array of length cArgs.
//                 It will be used to hold the parameters.
//
//  cArgs        - Number of pairs of generic arguments (below).
//
//  ...          - A collection of (VARNUM, LPVOID) pairs of arguments.
//                 The first is the type of the argument, and the
//                 second is the corresponding value.
//
//                 As a special case, a null VT_BSTR can be passed
//                 as a NULL pointer and we will turn it into a
//                 genuine null BSTR.
//
//  The following VARENUMs are supported:
//
//      VT_BYREF        - Anything that is VT_BYREF is okay
//      VT_BSTR
//      VT_BOOL
//      VT_DISPATCH
//      VT_UNKNOWN
//      VT_I4
//
//  Any other type will be packaged randomly, so don't do that.
//
//  Example:
//
//      DISPPARAMS dispparams;
//      VARIANTARG args[4];                     // room for 4 parameters
//      SHPackDispParams(&dispparams, args, 4,  // and here they are
//                       VT_BSTR,   bstrURL,
//                       VT_I4,     dwFlags,
//                       VT_BSTR,   NULL,       // no post data
//                       VT_BSTR,   bstrHeaders);
//

LWSTDAPI SHPackDispParams(DISPPARAMS *pdispparams, VARIANTARG *rgvarg, UINT cArgs, ...)
{
    va_list ap;
    va_start(ap, cArgs);

    HRESULT hr = SHPackDispParamsV(pdispparams, rgvarg, cArgs, ap);

    va_end(ap);
    return hr;
}

//=============================================================================
//
//  IConnectionPoint helper functions


//-----------------------------------------------------------------------------
//
//  INVOKECALLBACK
//
//  Allows clients to customize the the invoke process.  The callback
//  receives the following parameters:
//
//  pdisp       - The IDispatch that is about to receive an invoke.
//
//  pinv        - SHINVOKEPARAMS structure that describes the invoke
//                that is about to occur.
//
//  The callback function is called before each sink is dispatched.
//  The callback can return any of the following values:
//
//  S_OK          Proceed with the invoke
//  S_FALSE       Skip this invoke but keep invoking others
//  E_FAIL        Stop invoking
//
//  A client can do lazy-evaluation of dispatch arguments by installing
//  a callback that sets up the dispatch arguments on the first callback.
//
//  A client can support a "Cancel" flag by returning E_FAIL once the
//  cancel has occurred.
//
//  A client can pre-validate an IDispatch for compatibility reasons
//  and either touch up the arguments and return S_OK, or decide that
//  the IDispatch should be skipped and return S_FALSE.
//
//  A client can append custom information to the end of the SHINVOKEPARAMS
//  structure to allow it to determine additional context.
//
//  A client can do post-invoke goo by doing work on the pre-invoke
//  of the subsequent callback (plus one final bout of work when the
//  entire enumeration completes).
//

//
//  Obtaining a connection point sink is supposed to be easy.  You just
//  QI for the interface.  Unfortunately, too many components are buggy.
//
//  mmc.exe faults if you QI for IDispatch
//  and punkCB is non-NULL.  And if you do pass in NULL,
//  it returns S_OK but fills punkCB with NULL anyway.
//  Somebody must've had a rough day.
//
//  Java responds only to its dispatch ID and not IID_IDispatch, even
//  though the dispatch ID is derived from IID_IDispatch.
//
//  The Explorer Band responds only to IID_IDispatch and not to
//  the dispatch ID.
//

HRESULT GetConnectionPointSink(IUnknown *pUnk, const IID *piidCB, IUnknown **ppunkCB)
{
    HRESULT hr = E_NOINTERFACE;
    *ppunkCB = NULL;                // Pre-zero it to work around MMC
    if (piidCB)                     // Optional interface (Java/ExplBand)
    {                   
        hr = pUnk->QueryInterface(*piidCB, (void **) ppunkCB);
        if (*ppunkCB == NULL)       // Clean up behind MMC
            hr = E_NOINTERFACE;
    }
    return hr;
}


//
//  Enumerate the connection point sinks, calling the callback for each one
//  found.
//
//  The callback function is called once for each sink.  The IUnknown is
//  whatever interface we could get from the sink (either piidCB or piidCB2).
//

typedef HRESULT (CALLBACK *ENUMCONNECTIONPOINTSPROC)(
    /* [in, iid_is(*piidCB)] */ IUnknown *psink, LPARAM lParam);

HRESULT EnumConnectionPointSinks(
    IConnectionPoint *pcp,              // IConnectionPoint victim
    const IID *piidCB,                  // Interface for callback
    const IID *piidCB2,                 // Alternate interface for callback
    ENUMCONNECTIONPOINTSPROC EnumProc,  // Callback procedure
    LPARAM lParam)                      // Refdata for callback
{
    HRESULT hr;
    IEnumConnections * pec;

    if (pcp)
        hr = pcp->EnumConnections(&pec);
    else
        hr = E_NOINTERFACE;

    if (SUCCEEDED(hr))
    {
        CONNECTDATA cd;
        ULONG cFetched;

        while (S_OK == (hr = pec->Next(1, &cd, &cFetched)))
        {
            IUnknown *punkCB;

            ASSERT(1 == cFetched);

            hr = GetConnectionPointSink(cd.pUnk, piidCB, &punkCB);
            if (FAILED(hr))
                hr = GetConnectionPointSink(cd.pUnk, piidCB2, &punkCB);

            if (EVAL(SUCCEEDED(hr)))
            {
                hr = EnumProc(punkCB, lParam);
                punkCB->Release();
            }
            else
            {
                hr = S_OK;      // Pretend callback succeeded
            }
            cd.pUnk->Release();
            if (FAILED(hr)) break; // Callback asked to stop
        }
        pec->Release();
        hr = S_OK;
    }

    return hr;
}

//
//  Send out the callback (if applicable) and then do the invoke if the
//  callback said that was a good idea.
//
//  Parameters:
//
//      pcp          -  IConnectionPoint whose sinks are to be Invoke()d.
//                      If this parameter is NULL, the function does nothing.
//      pinv         -  Structure containing parameters to INVOKE.

HRESULT CALLBACK EnumInvokeCallback(IUnknown *psink, LPARAM lParam)
{
    IDispatch *pdisp = (IDispatch *)psink;
    LPSHINVOKEPARAMS pinv = (LPSHINVOKEPARAMS)lParam;
    HRESULT hr;

    if (pinv->Callback)
    {
        // Now see if the callback wants to do pre-vet the pdisp.
        // It can return S_FALSE to skip this callback or E_FAIL to
        // stop the invoke altogether
        hr = pinv->Callback(pdisp, pinv);
        if (hr != S_OK) return hr;
    }

    pdisp->Invoke(pinv->dispidMember, *pinv->piid, pinv->lcid,
                  pinv->wFlags, pinv->pdispparams, pinv->pvarResult,
                  pinv->pexcepinfo, pinv->puArgErr);

    return S_OK;
}

//
//  IConnectionPoint_InvokeIndirect
//
//  Given a connection point, call the IDispatch::Invoke for each
//  connected sink.
//
//  The return value merely indicates whether the command was dispatched.
//  If any particular sink fails the IDispatch::Invoke, we will still
//  return S_OK, since the command was indeed dispatched.
//
//  Parameters:
//
//      pcp          -  IConnectionPoint whose sinks are to be Invoke()d.
//                      If this parameter is NULL, the function does nothing.
//      pinv         -  Structure containing parameters to INVOKE.
//                      The pdispparams field can be NULL; we will turn it
//                      into a real DISPPARAMS for you.
//
//  The SHINVOKEPARAMS.flags field can contain the following flags.
//
//      IPFL_USECALLBACK    - The callback field contains a callback function
//                            Otherwise, it will be set to NULL.
//      IPFL_USEDEFAULT     - Many fields in the SHINVOKEPARAMS will be set to
//                            default values to save the caller effort:
//
//                  riid            =   IID_NULL
//                  lcid            =   0
//                  wFlags          =   DISPATCH_METHOD
//                  pvarResult      =   NULL
//                  pexcepinfo      =   NULL
//                  puArgErr        =   NULL
//

LWSTDAPI IConnectionPoint_InvokeIndirect(
    IConnectionPoint *pcp,
    SHINVOKEPARAMS *pinv)
{
    HRESULT hr;
    DISPPARAMS dp = { 0 };
    IID iidCP;

    if (pinv->pdispparams == NULL)
        pinv->pdispparams = &dp;

    if (!(pinv->flags & IPFL_USECALLBACK))
    {
        pinv->Callback = NULL;
    }

    if (pinv->flags & IPFL_USEDEFAULTS)
    {
        pinv->piid            =  &IID_NULL;
        pinv->lcid            =   0;
        pinv->wFlags          =   DISPATCH_METHOD;
        pinv->pvarResult      =   NULL;
        pinv->pexcepinfo      =   NULL;
        pinv->puArgErr        =   NULL;
    }

    // Try both the interface they actually connected on,
    // as well as IDispatch.  Apparently Java responds only to
    // the connecting interface, and ExplBand responds only to
    // IDispatch, so we have to try both.  (Sigh.  Too many buggy
    // components in the system.)

    hr = EnumConnectionPointSinks(pcp,
                                  (pcp->GetConnectionInterface(&iidCP) == S_OK) ? &iidCP : NULL,
                                  &IID_IDispatch,
                                  EnumInvokeCallback,
                                  (LPARAM)pinv);

    // Put the original NULL back so the caller can re-use the SHINVOKEPARAMS.
    if (pinv->pdispparams == &dp)
        pinv->pdispparams = NULL;

    return hr;
}

//
//  Wrapper around IConnectionPoint_InvokeIndirect with special Cancel
//  semantics.
//
//  Parameters:
//
//      pcp          -  IConnectionPoint whose sinks are to be Invoke()d.
//                      If this parameter is NULL, the function does nothing.
//      dispid       -  The DISPID to invoke
//      pdispparams  -  The DISPPARAMS for the invoke
//      pfCancel     -  Optional BOOL to cancel the invoke
//      ppvCancel    -  Optional LPVOID to cancel the invoke
//
//  If either *pfCancel or *ppvCancel is nonzero/non-NULL, we stop the invoke
//  process.  This allows a sink to "handle" the event and prevent other
//  sinks from receiving it.  The ppvCancel parameter is for dispid's which
//  are queries that are asking for somebody to create an object and return it.
//
//  It is the caller's responsibility to check the values of *pfCancel
//  and/or *ppvCancel to determine if the operation was cancelled.
//

typedef struct INVOKEWITHCANCEL {
    SHINVOKEPARAMS inv;
    LPBOOL pfCancel;
    void **ppvCancel;
} INVOKEWITHCANCEL;

HRESULT CALLBACK InvokeWithCancelProc(IDispatch *psink, SHINVOKEPARAMS *pinv)
{
    INVOKEWITHCANCEL *piwc = CONTAINING_RECORD(pinv, INVOKEWITHCANCEL, inv);

    if ((piwc->pfCancel && *piwc->pfCancel) ||
        (piwc->ppvCancel && *piwc->ppvCancel))
        return E_FAIL;

    return S_OK;
}

LWSTDAPI IConnectionPoint_InvokeWithCancel(
    IConnectionPoint *pcp,
    DISPID dispidMember,
    DISPPARAMS * pdispparams,
    LPBOOL pfCancel,
    void **ppvCancel)
{
    INVOKEWITHCANCEL iwc;

    iwc.inv.flags = IPFL_USECALLBACK | IPFL_USEDEFAULTS;
    iwc.inv.dispidMember = dispidMember;
    iwc.inv.pdispparams = pdispparams;
    iwc.inv.Callback = InvokeWithCancelProc;
    iwc.pfCancel = pfCancel;
    iwc.ppvCancel = ppvCancel;

    return IConnectionPoint_InvokeIndirect(pcp, &iwc.inv);
}

//
//  Wrapper around IConnectionPoint_InvokeIndirect with IPFL_USEDEFAULTS.
//

LWSTDAPI IConnectionPoint_SimpleInvoke(IConnectionPoint *pcp, DISPID dispidMember, DISPPARAMS *pdispparams)
{
    SHINVOKEPARAMS inv;

    inv.flags = IPFL_USEDEFAULTS;
    inv.dispidMember = dispidMember;
    inv.pdispparams = pdispparams;

    return IConnectionPoint_InvokeIndirect(pcp, &inv);
}

//
//  Takes a variable number of parameters for IDispatch, packages
//  them up, and invokes them.
//
//  The parameters to the IDispatch::Invoke will be
//
//      dispidMember    -   dispidMember
//      riid            -   IID_NULL
//      lcid            -   0
//      wFlags          -   DISPATCH_METHOD
//      pdispparams     -   <parameters to this function>
//      pvarResult      -   NULL
//      pexcepinfo      -   NULL
//      puArgErr        -   NULL
//
//  The parameters to this function are
//
//  pcp          - IConnectionPoint whose sinks should be Invoke()d.
//                 If this parameter is NULL, the function does nothing.
//  dispidMember - The DISPID to invoke.
//  rgvarg       - Array of length cArgs.
//                 It will be used to hold the parameters.
//  cArgs        - Number of pairs of generic arguments (below).
//
//  ap           - va_list of parameters to package.  We package up the
//                 first (2 * cArgs) of them.  See SHPackDispParams
//                 for details.
//

LWSTDAPI IConnectionPoint_InvokeParamV(IConnectionPoint *pcp, DISPID dispidMember, 
                                       VARIANTARG *rgvarg, UINT cArgs, va_list ap)
{
    HRESULT hr;

    if (pcp)
    {
        DISPPARAMS dp;
        hr = SHPackDispParamsV(&dp, rgvarg, cArgs, ap);
        if (EVAL(SUCCEEDED(hr)))
        {
            hr = IConnectionPoint_SimpleInvoke(pcp, dispidMember, &dp);
        }
    }
    else
        hr = E_NOINTERFACE;

    return hr;
}

//
//  Worker function for many classes of IConnectionPoint_Invoke
//  clients.  Just pass the parameters and we'll pack them up for you.
//
//  The parameters to the IDispatch::Invoke will be
//
//      dispidMember    -   dispidMember
//      riid            -   IID_NULL
//      lcid            -   0
//      wFlags          -   DISPATCH_METHOD
//      pdispparams     -   <parameters to this function>
//      pvarResult      -   NULL
//      pexcepinfo      -   NULL
//      puArgErr        -   NULL
//
//  The parameters to this function are
//
//  pcp          - IConnectionPoint whose sinks should be Invoke()d.
//                 If this parameter is NULL, the function does nothing.
//
//  dispidMember - The DISPID to invoke.
//
//  rgvarg       - Array of length cArgs.
//                 It will be used to hold the parameters.
//
//  cArgs        - Number of pairs of generic arguments (below).
//
//  ...          - A collection of (VARENUM, blah) pairs of arguments.
//                 See SHPackDispParams for details.
//
//  Example:
//
//      VARIANTARG args[1];
//      IConnectionPoint_InvokeParam(pcp, DISPID_PROPERTYCHANGE,
//                                   args, 1,
//                                   VT_BSTR, bstrProperty);

LWSTDAPIV IConnectionPoint_InvokeParam(IConnectionPoint *pcp, DISPID dispidMember, 
                                       VARIANTARG *rgvarg, UINT cArgs, ...)
{
    HRESULT hr;
    va_list ap;

    va_start(ap, cArgs);

    hr = IConnectionPoint_InvokeParamV(pcp, dispidMember, rgvarg, cArgs, ap);

    va_end(ap);

    return hr;
}

//
//  Given a connection point that represents IPropertyNotifySink,
//  call the IPropertyNotifySink::OnChanged for each connected sink.
//
//  Parameters:
//
//      pcp          -  IConnectionPoint whose sinks are to be notified.
//                      If this parameter is NULL, the function does nothing.
//      dispid       -  To pass to IPropertyNotifySink::OnChanged.

HRESULT CALLBACK OnChangedCallback(IUnknown *psink, LPARAM lParam)
{
    IPropertyNotifySink *pns = (IPropertyNotifySink *)psink;
    DISPID dispid = (DISPID)lParam;

    pns->OnChanged(dispid);

    return S_OK;
}

LWSTDAPI IConnectionPoint_OnChanged(IConnectionPoint *pcp, DISPID dispid)
{
#ifdef DEBUG
    // Make sure it really is an IPropertyNotifySink connection point.
    if (pcp)
    {
        IID iid;
        HRESULT hr = pcp->GetConnectionInterface(&iid);
        ASSERT(SUCCEEDED(hr) && iid == IID_IPropertyNotifySink);
    }
#endif
    return EnumConnectionPointSinks(pcp, &IID_IPropertyNotifySink, NULL,
                                    OnChangedCallback, (LPARAM)dispid);
}

//=============================================================================
//
//  IConnectionPointContainer helper functions

//
//  QI's for IConnectionPointContainer and then does the FindConnectionPoint.
//
//  Parameters:
//
//      punk         -  The object who might be an IConnectionPointContainer.
//                      This parameter may be NULL, in which case the
//                      operation fails.
//      riidCP       -  The connection point interface to locate.
//      pcpOut       -  Receives the IConnectionPoint, if any.

LWSTDAPI IUnknown_FindConnectionPoint(IUnknown *punk, REFIID riidCP, 
                                      IConnectionPoint **pcpOut)
{
    HRESULT hr;

    *pcpOut = NULL;

    if (punk)
    {
        IConnectionPointContainer *pcpc;
        hr = punk->QueryInterface(IID_IConnectionPointContainer, (void **)&pcpc);
        if (SUCCEEDED(hr))
        {
            hr = pcpc->FindConnectionPoint(riidCP, pcpOut);
            pcpc->Release();
        }
    }
    else
        hr = E_NOINTERFACE;

    return hr;
}

//
//  Given an IUnknown, query for its connection point container,
//  find the corresponding connection point, package up the
//  invoke parameters, and call the IDispatch::Invoke for each
//  connected sink.
//
//  See IConnectionPoint_InvokeParam for additional semantics.
//
//  Parameters:
//
//      punk         -  Object that might be an IConnectionPointContainer
//      riidCP       -  ConnectionPoint interface to request
//      pinv         -  Arguments for the Invoke.
//

LWSTDAPI IUnknown_CPContainerInvokeIndirect(IUnknown *punk, REFIID riidCP,
                SHINVOKEPARAMS *pinv)
{
    IConnectionPoint *pcp;
    HRESULT hr = IUnknown_FindConnectionPoint(punk, riidCP, &pcp);
    if (SUCCEEDED(hr))
    {
        hr = IConnectionPoint_InvokeIndirect(pcp, pinv);
        pcp->Release();
    }
    return hr;
}

//
//  This is the ultimate in one-stop shopping.
//
//  Given an IUnknown, query for its connection point container,
//  find the corresponding connection point, package up the
//  invoke parameters, and call the IDispatch::Invoke for each
//  connected sink.
//
//  See IConnectionPoint_InvokeParam for additional semantics.
//
//  Parameters:
//
//      punk         -  Object that might be an IConnectionPointContainer
//      riidCP       -  ConnectionPoint interface to request
//      dispidMember -  The DISPID to invoke.
//      rgvarg       -  Array of length cArgs.
//                      It will be used to hold the parameters.
//      cArgs        -  Number of pairs of generic arguments (below).
//      ...          -  A collection of (VARNUM, LPVOID) pairs of arguments.
//                      See SHPackDispParams for details.
//
//  Example:
//
//      IUnknown_CPContainerInvokeParam(punk, DIID_DShellFolderViewEvents,
//                                      DISPID_SELECTIONCHANGED, NULL, 0);

LWSTDAPIV IUnknown_CPContainerInvokeParam(
    IUnknown *punk, REFIID riidCP,
    DISPID dispidMember, VARIANTARG *rgvarg, UINT cArgs, ...)
{
    IConnectionPoint *pcp;
    HRESULT hr = IUnknown_FindConnectionPoint(punk, riidCP, &pcp);

    if (SUCCEEDED(hr))
    {
        va_list ap;
        va_start(ap, cArgs);
        hr = IConnectionPoint_InvokeParamV(pcp, dispidMember, rgvarg, cArgs, ap);
        va_end(ap);
        pcp->Release();
    }

    return hr;
}

//
//  Given an IUnknown, query for its connection point container,
//  find the corresponding connection point, and call the
//  IPropertyNotifySink::OnChanged for each connected sink.
//
//  Parameters:
//
//      punk         -  Object that might be an IConnectionPointContainer
//      dispid       -  To pass to IPropertyNotifySink::OnChanged.

LWSTDAPI IUnknown_CPContainerOnChanged(IUnknown *punk, DISPID dispid)
{
    IConnectionPoint *pcp;
    HRESULT hr = IUnknown_FindConnectionPoint(punk, IID_IPropertyNotifySink, &pcp);
    if (SUCCEEDED(hr))
    {
        hr = IConnectionPoint_OnChanged(pcp, dispid);
        pcp->Release();
    }
    return hr;
}
