644 lines
11 KiB
C++
644 lines
11 KiB
C++
|
#include "sqleval.h"
|
||
|
#include <stdio.h>
|
||
|
#include <genlex.h>
|
||
|
#include <sqllex.h>
|
||
|
#include <sql_1.h>
|
||
|
|
||
|
// CSqlWmiEvalee
|
||
|
CSqlWmiEvalee::~CSqlWmiEvalee()
|
||
|
{
|
||
|
if(m_pInstance)
|
||
|
m_pInstance->Release();
|
||
|
}
|
||
|
|
||
|
|
||
|
CSqlWmiEvalee::CSqlWmiEvalee(
|
||
|
IWbemClassObject* pInst)
|
||
|
:m_pInstance(NULL)
|
||
|
{
|
||
|
m_pInstance = pInst;
|
||
|
if(m_pInstance)
|
||
|
m_pInstance->AddRef();
|
||
|
VariantInit(&m_v);
|
||
|
}
|
||
|
|
||
|
const VARIANT*
|
||
|
CSqlWmiEvalee::Get(
|
||
|
WCHAR* wszName)
|
||
|
{
|
||
|
VariantClear(&m_v);
|
||
|
BSTR bstr = SysAllocString(wszName);
|
||
|
if (bstr != NULL)
|
||
|
{
|
||
|
SCODE sc = m_pInstance->Get(
|
||
|
bstr,
|
||
|
0,
|
||
|
&m_v,
|
||
|
NULL,
|
||
|
NULL);
|
||
|
SysFreeString(bstr);
|
||
|
if(sc != S_OK)
|
||
|
throw sc;
|
||
|
}
|
||
|
|
||
|
return &m_v;
|
||
|
}
|
||
|
// CSqlEval
|
||
|
|
||
|
CSqlEval*
|
||
|
CSqlEval::CreateClass(
|
||
|
SQL_LEVEL_1_RPN_EXPRESSION* pExpr,
|
||
|
int* pNumberOfToken)
|
||
|
{
|
||
|
if(pExpr== NULL || *pNumberOfToken<= 0)
|
||
|
return NULL;
|
||
|
SQL_LEVEL_1_TOKEN* pToken = pExpr->pArrayOfTokens+(*pNumberOfToken-1);
|
||
|
|
||
|
// (*pNumberOfToken)--;
|
||
|
switch(pToken->nTokenType)
|
||
|
{
|
||
|
case SQL_LEVEL_1_TOKEN::TOKEN_AND:
|
||
|
return new CSqlEvalAnd(
|
||
|
pExpr,
|
||
|
pNumberOfToken);
|
||
|
case SQL_LEVEL_1_TOKEN::TOKEN_OR:
|
||
|
return new CSqlEvalOr(
|
||
|
pExpr,
|
||
|
pNumberOfToken);
|
||
|
case SQL_LEVEL_1_TOKEN::OP_EXPRESSION:
|
||
|
return new CSqlEvalExp(
|
||
|
pExpr,
|
||
|
pNumberOfToken);
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
CSqlEval::Evaluate(
|
||
|
CSqlEvalee* pInst)
|
||
|
{
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
// CSqlEvalAnd
|
||
|
CSqlEvalAnd::~CSqlEvalAnd()
|
||
|
{
|
||
|
delete m_left;
|
||
|
delete m_right;
|
||
|
}
|
||
|
|
||
|
CSqlEvalAnd::CSqlEvalAnd(
|
||
|
SQL_LEVEL_1_RPN_EXPRESSION* pExpr,
|
||
|
int* pTokens)
|
||
|
:m_left(NULL),m_right(NULL)
|
||
|
{
|
||
|
(*pTokens)-- ;
|
||
|
m_left = CSqlEval::CreateClass(
|
||
|
pExpr,
|
||
|
pTokens);
|
||
|
m_right = CSqlEval::CreateClass(
|
||
|
pExpr,
|
||
|
pTokens);
|
||
|
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
CSqlEvalAnd::Evaluate(
|
||
|
CSqlEvalee* pInst)
|
||
|
{
|
||
|
return m_left->Evaluate(pInst) && m_right->Evaluate(pInst);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
CSqlEvalAnd::GenerateQueryEnum(CQueryEnumerator& qeInst)
|
||
|
{
|
||
|
CQueryEnumerator qeNew = qeInst;
|
||
|
m_left->GenerateQueryEnum(qeNew);
|
||
|
|
||
|
m_right->GenerateQueryEnum(qeInst);
|
||
|
qeInst.And(qeNew);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// CSqlEvalOR
|
||
|
CSqlEvalOr::~CSqlEvalOr()
|
||
|
{
|
||
|
delete m_left;
|
||
|
delete m_right;
|
||
|
}
|
||
|
|
||
|
CSqlEvalOr::CSqlEvalOr(
|
||
|
SQL_LEVEL_1_RPN_EXPRESSION* pExpr,
|
||
|
int* pTokens)
|
||
|
:m_left(NULL),m_right(NULL)
|
||
|
{
|
||
|
(*pTokens)-- ;
|
||
|
m_left = CSqlEval::CreateClass(
|
||
|
pExpr,
|
||
|
pTokens);
|
||
|
m_right = CSqlEval::CreateClass(
|
||
|
pExpr,
|
||
|
pTokens);
|
||
|
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
CSqlEvalOr::Evaluate(
|
||
|
CSqlEvalee* pInst)
|
||
|
{
|
||
|
return m_left->Evaluate(pInst) || m_right->Evaluate(pInst);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
CSqlEvalOr::GenerateQueryEnum(CQueryEnumerator& qeInst)
|
||
|
{
|
||
|
CQueryEnumerator qeNew = qeInst;
|
||
|
m_left->GenerateQueryEnum(qeNew);
|
||
|
|
||
|
m_right->GenerateQueryEnum(qeInst);
|
||
|
qeInst.Or(qeNew);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// CSqlEvalExp
|
||
|
CSqlEvalExp::~CSqlEvalExp()
|
||
|
{
|
||
|
SysFreeString(m_BstrName);
|
||
|
VariantClear(&m_v);
|
||
|
}
|
||
|
|
||
|
CSqlEvalExp::CSqlEvalExp(
|
||
|
SQL_LEVEL_1_RPN_EXPRESSION* pExpr,
|
||
|
int* pTokens)
|
||
|
:m_BstrName(NULL)
|
||
|
{
|
||
|
VariantInit(&m_v);
|
||
|
SQL_LEVEL_1_TOKEN* pToken = pExpr->pArrayOfTokens+(*pTokens-1);
|
||
|
(*pTokens)--;
|
||
|
m_BstrName = SysAllocString(pToken->pPropertyName);
|
||
|
VariantCopy(&m_v, &(pToken->vConstValue));
|
||
|
m_op = pToken->nOperator;
|
||
|
switch(m_v.vt)
|
||
|
{
|
||
|
case VT_I2:
|
||
|
m_dw = m_v.iVal;
|
||
|
m_DataType = IntergerType;
|
||
|
break;
|
||
|
case VT_I4:
|
||
|
m_dw = m_v.lVal;
|
||
|
m_DataType = IntergerType;
|
||
|
break;
|
||
|
case VT_BOOL:
|
||
|
m_DataType = IntergerType;
|
||
|
m_dw = m_v.boolVal;
|
||
|
break;
|
||
|
case VT_BSTR:
|
||
|
m_DataType = StringType;
|
||
|
m_bstr = m_v.bstrVal;
|
||
|
break;
|
||
|
default:
|
||
|
throw WBEM_E_INVALID_PARAMETER;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
CSqlEvalExp::Evaluate(
|
||
|
CSqlEvalee* pInst)
|
||
|
{
|
||
|
|
||
|
BOOL Result=FALSE;
|
||
|
const VARIANT* pv = pInst->Get(m_BstrName);
|
||
|
|
||
|
switch(m_DataType)
|
||
|
{
|
||
|
case IntergerType:
|
||
|
DWORD dw;
|
||
|
switch(pv->vt)
|
||
|
{
|
||
|
case VT_I2:
|
||
|
dw = pv->iVal;
|
||
|
break;
|
||
|
case VT_I4:
|
||
|
dw = pv->lVal;
|
||
|
break;
|
||
|
case VT_BOOL:
|
||
|
dw = pv->boolVal;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// compare
|
||
|
switch(m_op)
|
||
|
{
|
||
|
case SQL_LEVEL_1_TOKEN::OP_EQUAL:
|
||
|
Result = (dw == m_dw);
|
||
|
break;
|
||
|
case SQL_LEVEL_1_TOKEN::OP_NOT_EQUAL:
|
||
|
Result = !(dw == m_dw);
|
||
|
break;
|
||
|
case SQL_LEVEL_1_TOKEN::OP_EQUALorGREATERTHAN:
|
||
|
Result = (dw >= m_dw);
|
||
|
break;
|
||
|
case SQL_LEVEL_1_TOKEN::OP_EQUALorLESSTHAN:
|
||
|
Result = (dw <= m_dw);
|
||
|
break;
|
||
|
case SQL_LEVEL_1_TOKEN::OP_LESSTHAN:
|
||
|
Result = (dw < m_dw);
|
||
|
break;
|
||
|
case SQL_LEVEL_1_TOKEN::OP_GREATERTHAN:
|
||
|
Result = (dw > m_dw);
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case StringType:
|
||
|
int rt = _wcsicmp(pv->bstrVal, m_bstr);
|
||
|
switch(m_op)
|
||
|
{
|
||
|
case SQL_LEVEL_1_TOKEN::OP_EQUAL:
|
||
|
Result = (rt == 0);
|
||
|
break;
|
||
|
case SQL_LEVEL_1_TOKEN::OP_NOT_EQUAL:
|
||
|
Result = !(rt == 0);
|
||
|
break;
|
||
|
case SQL_LEVEL_1_TOKEN::OP_EQUALorGREATERTHAN:
|
||
|
Result = (rt > 0 || rt == 0);
|
||
|
break;
|
||
|
case SQL_LEVEL_1_TOKEN::OP_EQUALorLESSTHAN:
|
||
|
Result = (rt < 0 || rt == 0);
|
||
|
break;
|
||
|
case SQL_LEVEL_1_TOKEN::OP_LESSTHAN:
|
||
|
Result = (rt < 0);
|
||
|
break;
|
||
|
case SQL_LEVEL_1_TOKEN::OP_GREATERTHAN:
|
||
|
Result = (rt > 0 );
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
CSqlEvalExp::GenerateQueryEnum(CQueryEnumerator& qeInst)
|
||
|
{
|
||
|
int nSize = qeInst.m_QueryFields.Size();
|
||
|
const WCHAR** ppName = qeInst.m_QueryFields.Data();
|
||
|
WCHAR** ppWstr = new WCHAR*[nSize];
|
||
|
if ( ppWstr )
|
||
|
{
|
||
|
for(int i=0; i< nSize; i++)
|
||
|
{
|
||
|
if(_wcsicmp( m_BstrName, ppName[i]) == 0)
|
||
|
{
|
||
|
ppWstr[i] = m_v.bstrVal;
|
||
|
}
|
||
|
else
|
||
|
ppWstr[i] = NULL;
|
||
|
}
|
||
|
CQueryEnumerator::CStringArray strArray(
|
||
|
ppWstr,
|
||
|
nSize);
|
||
|
delete [] ppWstr;
|
||
|
qeInst.ArrayAdd(strArray);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//CQueryEnumerator;
|
||
|
CQueryEnumerator::CQueryEnumerator(
|
||
|
WCHAR** ppKeyName,
|
||
|
int cArg ):
|
||
|
m_index(0),m_cNumOfRecordInSet(0),
|
||
|
m_QuerySet(NULL), m_MaxSize(INITIAL_SIZE)
|
||
|
{
|
||
|
m_QueryFields = CStringArray(
|
||
|
ppKeyName,
|
||
|
cArg);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
CQueryEnumerator::CQueryEnumerator(
|
||
|
CQueryEnumerator& instEnumerator)
|
||
|
:m_index(0), m_MaxSize(INITIAL_SIZE),
|
||
|
m_QuerySet(NULL), m_cNumOfRecordInSet(0)
|
||
|
{
|
||
|
|
||
|
m_QueryFields = instEnumerator.m_QueryFields;
|
||
|
int nSize = instEnumerator.m_cNumOfRecordInSet;
|
||
|
for(int i=0; i< nSize; i++)
|
||
|
{
|
||
|
ArrayAdd(instEnumerator.m_QuerySet[i]);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
CQueryEnumerator::~CQueryEnumerator()
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
DWORD
|
||
|
CQueryEnumerator::InitEnumerator(
|
||
|
WCHAR** wszFieldNames,
|
||
|
int cArg,
|
||
|
CSqlEval* pEval)
|
||
|
{
|
||
|
m_QueryFields = CStringArray(
|
||
|
wszFieldNames,
|
||
|
cArg);
|
||
|
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
*/
|
||
|
const
|
||
|
WCHAR**
|
||
|
CQueryEnumerator::GetNext(int& cElement)
|
||
|
{
|
||
|
if(m_index == m_cNumOfRecordInSet)
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cElement = m_QuerySet[m_index].Size();
|
||
|
return m_QuerySet[m_index++].Data();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
CQueryEnumerator::ArrayMerge(
|
||
|
CQueryEnumerator::CStringArray& strArray)
|
||
|
{
|
||
|
|
||
|
for(int i=0; i< m_cNumOfRecordInSet; i++)
|
||
|
{
|
||
|
// if all element of strArray are null, no need to proceed
|
||
|
if( !strArray.IsNULL())
|
||
|
m_QuerySet[i].Merge(strArray);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
CQueryEnumerator::ArrayAdd(
|
||
|
CQueryEnumerator::CStringArray& strArray)
|
||
|
{
|
||
|
// if all elements are null for strArray, means no keys are
|
||
|
// selected,we should therefore replace M_querySet this StrArray
|
||
|
if(strArray.IsNULL())
|
||
|
ArrayDelete();
|
||
|
|
||
|
if(m_QuerySet == NULL)
|
||
|
{
|
||
|
m_QuerySet = new CStringArray[m_MaxSize];
|
||
|
if ( !m_QuerySet )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if array is full, then expand
|
||
|
if(m_index == m_MaxSize)
|
||
|
{
|
||
|
CStringArray * pOldSet = m_QuerySet;
|
||
|
m_MaxSize = m_MaxSize *2;
|
||
|
m_QuerySet = new CStringArray[m_MaxSize];
|
||
|
if ( !m_QuerySet )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
for( int i =0; i < m_MaxSize; i++ )
|
||
|
{
|
||
|
if( i < m_index )
|
||
|
{
|
||
|
m_QuerySet[ i ] = pOldSet[ i ];
|
||
|
}
|
||
|
}
|
||
|
delete [] pOldSet;
|
||
|
}
|
||
|
|
||
|
if(!( m_cNumOfRecordInSet> 0 && (m_QuerySet[0]).IsNULL()))
|
||
|
{
|
||
|
m_QuerySet[m_index++] = strArray;
|
||
|
m_cNumOfRecordInSet = m_index;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
CQueryEnumerator::ArrayDelete()
|
||
|
{
|
||
|
delete [] m_QuerySet;
|
||
|
m_QuerySet = NULL;
|
||
|
m_cNumOfRecordInSet = 0;
|
||
|
m_MaxSize = INITIAL_SIZE;
|
||
|
m_index = 0;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
CQueryEnumerator::And(
|
||
|
CQueryEnumerator& instEnumerator)
|
||
|
{
|
||
|
int nSize = instEnumerator.m_cNumOfRecordInSet;
|
||
|
if(nSize > 0)
|
||
|
{
|
||
|
CQueryEnumerator qeOld= *this;
|
||
|
ArrayDelete();
|
||
|
for(int i=0; i< nSize; i++)
|
||
|
{
|
||
|
CQueryEnumerator qeNew = qeOld;
|
||
|
if ( !qeNew.m_QuerySet )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
qeNew.ArrayMerge(instEnumerator.m_QuerySet[i]);
|
||
|
for(int j=0; j< qeNew.m_cNumOfRecordInSet; j++)
|
||
|
{
|
||
|
ArrayAdd(qeNew.m_QuerySet[j]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
CQueryEnumerator::Or(
|
||
|
CQueryEnumerator& instEnumerator)
|
||
|
{
|
||
|
for(int i=0; i< instEnumerator.m_cNumOfRecordInSet; i++)
|
||
|
{
|
||
|
|
||
|
if(instEnumerator.m_QuerySet[i].IsNULL())
|
||
|
{
|
||
|
if(m_cNumOfRecordInSet > 0)
|
||
|
ArrayDelete();
|
||
|
ArrayAdd(instEnumerator.m_QuerySet[i]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ArrayAdd(instEnumerator.m_QuerySet[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
void
|
||
|
CQueryEnumerator::Reset()
|
||
|
{
|
||
|
m_index = 0;
|
||
|
}
|
||
|
// CStringArray
|
||
|
|
||
|
CQueryEnumerator::CStringArray::CStringArray()
|
||
|
:m_ppWstr(NULL), m_cNumString(0), m_bIsNull(TRUE)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
CQueryEnumerator::CStringArray::~CStringArray()
|
||
|
{
|
||
|
if(m_ppWstr != NULL)
|
||
|
{
|
||
|
for (int i=0; i< m_cNumString; i++)
|
||
|
{
|
||
|
delete [] m_ppWstr[i];
|
||
|
}
|
||
|
delete [] m_ppWstr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CQueryEnumerator::CStringArray::CStringArray(
|
||
|
WCHAR **ppWstrInput,
|
||
|
int cNumString)
|
||
|
:m_ppWstr(NULL)
|
||
|
{
|
||
|
m_cNumString = cNumString;
|
||
|
m_bIsNull = !StringArrayCopy(
|
||
|
&m_ppWstr,
|
||
|
ppWstrInput,
|
||
|
cNumString);
|
||
|
|
||
|
}
|
||
|
CQueryEnumerator::CStringArray::CStringArray(
|
||
|
CQueryEnumerator::CStringArray& strArray)
|
||
|
:m_ppWstr(NULL)
|
||
|
{
|
||
|
m_cNumString = strArray.m_cNumString;
|
||
|
m_bIsNull = !StringArrayCopy(
|
||
|
&m_ppWstr,
|
||
|
strArray.m_ppWstr,
|
||
|
strArray.m_cNumString);
|
||
|
}
|
||
|
|
||
|
CQueryEnumerator::CStringArray&
|
||
|
CQueryEnumerator::CStringArray::operator =(
|
||
|
CQueryEnumerator::CStringArray& strArray)
|
||
|
{
|
||
|
if(m_ppWstr != NULL)
|
||
|
{
|
||
|
for (int i=0; i< m_cNumString; i++)
|
||
|
{
|
||
|
delete [] *m_ppWstr;
|
||
|
*m_ppWstr = NULL;
|
||
|
}
|
||
|
delete [] m_ppWstr;
|
||
|
m_ppWstr = NULL;
|
||
|
}
|
||
|
|
||
|
m_cNumString = strArray.m_cNumString;
|
||
|
m_bIsNull= !StringArrayCopy(
|
||
|
&m_ppWstr,
|
||
|
strArray.m_ppWstr,
|
||
|
strArray.m_cNumString);
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
int CQueryEnumerator::CStringArray::Size()
|
||
|
{
|
||
|
return m_cNumString;
|
||
|
}
|
||
|
|
||
|
const
|
||
|
WCHAR**
|
||
|
CQueryEnumerator::CStringArray::Data()
|
||
|
{
|
||
|
return (const WCHAR**) m_ppWstr;
|
||
|
}
|
||
|
|
||
|
// return true if any element is copied,
|
||
|
// false if no element is copied, and no element set to NULL
|
||
|
|
||
|
BOOL
|
||
|
CQueryEnumerator::CStringArray::StringArrayCopy(
|
||
|
WCHAR*** pppDest,
|
||
|
WCHAR** ppSrc,
|
||
|
int cArgs)
|
||
|
{
|
||
|
BOOL bFlag = FALSE;
|
||
|
if(cArgs >0 && ppSrc != NULL)
|
||
|
{
|
||
|
*pppDest = new WCHAR*[cArgs];
|
||
|
if ( *pppDest )
|
||
|
{
|
||
|
for(int i=0; i< cArgs; i++)
|
||
|
{
|
||
|
(*pppDest)[i] = NULL;
|
||
|
if(ppSrc[i] != NULL)
|
||
|
{
|
||
|
int len = wcslen(ppSrc[i]);
|
||
|
(*pppDest)[i] = new WCHAR[len+1];
|
||
|
if ( (*pppDest)[i] == NULL )
|
||
|
{
|
||
|
throw WBEM_E_OUT_OF_MEMORY;
|
||
|
}
|
||
|
wcscpy((*pppDest)[i], ppSrc[i]);
|
||
|
bFlag = TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return bFlag;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
CQueryEnumerator::CStringArray::Merge(
|
||
|
CQueryEnumerator::CStringArray& instStrArray)
|
||
|
{
|
||
|
if(instStrArray.Size() != m_cNumString)
|
||
|
throw WBEM_E_FAILED;
|
||
|
const WCHAR** ppSrc = instStrArray.Data();
|
||
|
for(int i=0; i<m_cNumString; i++)
|
||
|
{
|
||
|
// if source is null, we leave target string as it was
|
||
|
if( ppSrc[i] != NULL)
|
||
|
{
|
||
|
if(m_ppWstr[i] == NULL)
|
||
|
{
|
||
|
m_ppWstr[i] = new WCHAR[wcslen(ppSrc[i])+1];
|
||
|
if ( m_ppWstr[i] == NULL )
|
||
|
{
|
||
|
throw WBEM_E_OUT_OF_MEMORY;
|
||
|
}
|
||
|
wcscpy(m_ppWstr[i], ppSrc[i]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// a key can not take two different value
|
||
|
if(_wcsicmp(m_ppWstr[i], ppSrc[i]) != 0)
|
||
|
throw WBEM_E_INVALID_PARAMETER;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|