home *** CD-ROM | disk | FTP | other *** search
- //
- //----------------------------------------------------------------------------
- // ObjectComponents
- // (C) Copyright 1994 by Borland International, All Rights Reserved
- //
- // OLE Automation Server Implementation, except TServedObject (in typelib.cpp)
- //----------------------------------------------------------------------------
- #include <ocf/ocfpch.h>
- #include <ocf/appdesc.h>
- #include <ocf/ocreg.h>
-
- TAutoType TAutoVoid ::ClassInfo = {atVoid};
- TAutoType TAutoByte ::ClassInfo = {atByte};
- TAutoType TAutoShort ::ClassInfo = {atShort};
- TAutoType TAutoLong ::ClassInfo = {atLong};
- TAutoType TAutoFloat ::ClassInfo = {atFloat};
- TAutoType TAutoDouble ::ClassInfo = {atDouble};
- TAutoType TAutoCurrency ::ClassInfo = {atCurrency};
- TAutoType TAutoDate ::ClassInfo = {atDatetime};
- TAutoType TAutoString ::ClassInfo = {atString};
- TAutoType TAutoBool ::ClassInfo = {atBool};
- TAutoType TAutoUnknown ::ClassInfo = {atUnknown};
- TAutoType TAutoDispatch ::ClassInfo = {atObject};
- TAutoType TAutoShortRef ::ClassInfo = {atByRef|atShort};
- TAutoType TAutoLongRef ::ClassInfo = {atByRef|atLong};
- TAutoType TAutoFloatRef ::ClassInfo = {atByRef|atFloat};
- TAutoType TAutoDoubleRef ::ClassInfo = {atByRef|atDouble};
- TAutoType TAutoCurrencyRef::ClassInfo = {atByRef|atCurrency};
- TAutoType TAutoDateRef ::ClassInfo = {atByRef|atDatetime};
- TAutoType TAutoStringRef ::ClassInfo = {atByRef|atString};
- TAutoType TAutoVariantRef ::ClassInfo = {atByRef|atVariant};
- TAutoType TAutoBoolRef ::ClassInfo = {atByRef|atBool};
- TAutoType TAutoByteRef ::ClassInfo = {atByRef|atByte};
-
- void SendObituary(const void far* obj, const typeinfo& typeInfo)
- {
- TAppDescriptor* appDesc = ::GetAppDescriptor();
- if (appDesc)
- appDesc->InvalidateObject(::MostDerived(obj, typeInfo));
- }
-
- //____________________________________________________________________________
- //
- // TAutoClass implementation
- //____________________________________________________________________________
-
- TAutoClass::TClassList TAutoClass::ClassList = {0,0,0};// MUST BE MODULE GLOBAL
-
- TAutoClass::TAutoClass(TAutoSymbol* table, TAutoSymbol* classSymbol,
- const typeinfo& typeInfo, TAggregator aggregator)
- : Table(table), ClassSymbol(classSymbol), TypeInfo(typeInfo),
- Aggregator(aggregator)
- {
- Type = atObject | atAutoClass;
- NextClass = ClassList.List;
- ClassList.List = this;
- ClassList.Count++;
- }
-
- TAutoClass::~TAutoClass() // do we really need to support dynamic AutoClass?
- {
- for (TAutoClass** link = &ClassList.List; *link != 0; link = &(*link)->NextClass)
- if (*link == this) {
- *link = NextClass;
- break;
- }
- }
-
- short TAutoClass::CountCommands()
- {
- TAutoSymbol* sym;
- if (!CommandCount) {
- for (sym = Table; !sym->IsTerminator(); sym++) {
- int attr = sym->GetFlags();
- if (attr & asAnyCommand) {
- CommandCount++;
- if (attr & asOleType) {
- if ((attr & asGetSet) == asGetSet)
- VariableCount++;
- else
- FunctionCount++;
- }
- } else if (sym->TestFlag(asClass)) {
- TAutoClass* cls = sym->GetClass();
- if (!sym->SymCount)
- sym->SymCount = cls->CountCommands();
- CommandCount += cls->CommandCount;
- VariableCount += cls->VariableCount;
- FunctionCount += cls->FunctionCount;
- }
- }
- }
- return CommandCount;
- }
-
- TAutoSymbol* TAutoClass::FindId(short id, ObjectPtr& obj)
- {
- TAutoSymbol* sym;
- int cmdId;
- if (id <= 0) { // reserved dispatch ID if negative or zero
- for (sym = Table; !sym->IsTerminator(); sym++) {
- if (sym->TestFlag(asAnyCommand) && sym->DispId == id)
- return sym;
-
- if (sym->TestFlag(asClass)) {
- ObjectPtr adjObj = sym->Convert(obj); // this pointer adjustment
- TAutoSymbol* fsym = sym->GetClass()->FindId(id, adjObj);
- if (fsym) {
- obj = adjObj;
- return fsym;
- }
- }
- }
- } else {
- for (cmdId = 0, sym = Table; !sym->IsTerminator(); sym++) {
- if (sym->TestFlag(asClass)) {
- if (!sym->SymCount)
- sym->SymCount = sym->GetClass()->CountCommands();
- if (cmdId + sym->SymCount >= id) { // symbol in nested class
- obj = sym->Convert(obj);
- return sym->GetClass()->FindId(short(id-cmdId), obj);
- }
- cmdId += sym->SymCount;
- }
- else if (sym->TestFlag(asAnyCommand)) {
- cmdId++;
- if (cmdId == id)
- return sym;
- }
- }
- }
- return 0;
- }
-
- TAutoSymbol* TAutoClass::FindFunction(unsigned index, int& retId)
- {
- TAutoSymbol* sym;
- int funcCount = 0;
- int cmdId = retId;
- for (sym = Table; !sym->IsTerminator(); sym++) {
- int attr = sym->GetFlags();
- if (attr & asAnyCommand) {
- cmdId++;
- if ((attr & asOleType) != 0 && (attr & asGetSet) != asGetSet) {
- if (funcCount++ == index) {
- retId = sym->DispId == -1 ? cmdId : sym->DispId;
- return sym;
- }
- }
- } else if (sym->TestFlag(asClass)) {
- TAutoClass* cls = sym->GetClass();
- if (!sym->SymCount)
- sym->SymCount = cls->CountCommands();
- if (funcCount + cls->FunctionCount > index) {
- retId = cmdId;
- return cls->FindFunction(index - funcCount, retId);
- }
- funcCount += cls->FunctionCount;
- cmdId += cls->CommandCount;
- }
- }
- return 0; // should never happen unless caller overruns total count
- }
-
- TAutoSymbol* TAutoClass::FindVariable(unsigned index, int& retId)
- {
- TAutoSymbol* sym;
- int varCount = 0;
- int cmdId = retId;
- for (sym = Table; !sym->IsTerminator(); sym++) {
- int attr = sym->GetFlags();
- if (attr & asAnyCommand) {
- cmdId++;
- if ((attr & asGetSet) == asGetSet) {
- if (varCount++ == index) {
- retId = sym->DispId == -1 ? cmdId : sym->DispId;
- return sym;
- }
- }
- } else if (sym->TestFlag(asClass)) {
- TAutoClass* cls = sym->GetClass();
- if (!sym->SymCount)
- sym->SymCount = cls->CountCommands();
- if (varCount + cls->VariableCount > index) {
- retId = cmdId;
- return cls->FindVariable(index - varCount, retId);
- }
- varCount += cls->VariableCount;
- cmdId += cls->CommandCount;
- }
- }
- return 0; // should never happen unless caller overruns total count
- }
-
- short TAutoClass::GetArgCount(TAutoSymbol& sym)
- {
- short count = 0;
- TAutoSymbol* arg = &sym;
- while ((++arg)->TestFlag(asArgument))
- count++;
- return count;
- }
-
- TAutoSymbol* TAutoClass::Lookup(char far* name, TLangId lang, short symflags,
- long far& retid)
- {
- int cmdId = 0;
- for (TAutoSymbol* sym = Table; !sym->IsTerminator(); sym++) {
- if (sym->TestFlag(asAnyCommand))
- cmdId++;
- if (sym->TestFlag(symflags) && sym->Name.Compare(name, lang) == 0) {
- retid = sym->DispId == -1 ? (long)cmdId : (long)sym->DispId;
- return sym;
- }
- else if (sym->TestFlag(asClass)) {
- TAutoClass* cls = sym->GetClass();
- if (!sym->SymCount)
- sym->SymCount = cls->CountCommands();
- long id;
- TAutoSymbol* found = cls->Lookup(name, lang, symflags, id);
- if (found) {
- retid = id > 0 ? id + (long)cmdId : id;
- return found;
- }
- cmdId += sym->SymCount;
- }
- }
- return 0;
- }
-
- TAutoSymbol* TAutoClass::LookupArg(char far* name, TLangId lang,
- TAutoSymbol* sym, long far& retid)
- {
- PRECONDITION(sym);
-
- for (int i = 0; (++sym)->TestFlag(asArgument); ++i)
- if (sym->Name.Compare(name, lang) == 0) {
- retid = (long)i;
- return sym;
- }
- return 0;
- }
-
- TAutoCommand* AutoQuitBuild(ObjectPtr obj, int/*attr*/, TAutoStack& args)
- {
- TServedObject& owner = *args.Owner;
-
- // if the automation object is not in control of the app, execute a no-op
- //
- if (owner.Destruct == TObjectDescriptor::Quiet)
- return new TAutoCommand(0);
-
- // if registered as the active object, free it to release OLE's refcnt
- //
- if (owner.Creator.AppDesc.IsActiveObject(&owner))
- owner.Creator.AppDesc.UnregisterObject();
-
- // disconnect automation from app to prevent further access
- //
- owner.Object = 0;
- owner.RootObject = 0;
-
- // build command object for destructor, will either delete or PostQuitMsg
- //
- return owner.Class->GetDestructor()(obj, owner.Destruct);
- }
-
- TXAuto::TError TAutoClass::Dispatch(ObjectPtr obj, TAutoCreator& creator,
- TUnknown& owner, int attr, TAutoStack& args, TAutoVal far* retval)
- {
- TAutoCommand* cmdobj = 0;
- TAutoIterator* iterator = 0;
- try {
- if (args.Symbol->IsIterator()) {
- iterator = args.Symbol->BuildIter(obj, creator, owner, args.LangId);
- iterator->SetSymbol(args.Symbol);
- iterator->Init();
- *retval = (IUnknown*)*iterator; // remains until RefCnt->0
- } else {
- cmdobj = args.Symbol->Build(obj, attr, args);
- cmdobj->SetSymbol(args.Symbol);
- if (args.ArgCount>0 && !cmdobj->Validate()) {// no validate for prop get
- delete cmdobj;
- return TXAuto::xValidateFailure;
- }
- cmdobj->Invoke();
- if ((args.ErrorCode = cmdobj->Report()) != 0) {
- args.ErrorMsg = TAutoCommand::LookupError(args.ErrorCode);
- if (!args.ErrorMsg && args.Symbol) // if no error message available
- args.ErrorMsg = args.Symbol->Name.Translate(args.LangId);
- delete cmdobj;
- return TXAuto::xErrorStatus;
- }
- if (retval) {
- cmdobj->Return(*retval);
- if (args.Symbol->IsEnum())
- args.Symbol->GetEnum()->Convert(*retval, args.LangId);
- TObjectDescriptor objDesc;
- if (retval->GetObjDesc(objDesc)) {
- if (!objDesc.Object) // null pointer returned from function
- // there are three choices for behavior here:
- // 1. Allow a dead object to be returned, fail when passed back
- // 2. Fail now, however this prevents testing for null pointer
- // 3. Return an empty variant, causing script to fail when used
- *retval = TAutoVoid(); // return an empty value if no object
- else
- *retval = creator.CreateDispatch(objDesc);
- }
- }
- delete cmdobj;
- }
- }
- catch(TXAuto& xobj) {
- delete cmdobj;
- delete iterator;
- return xobj.ErrorCode;
- }
- return TXAuto::xNoError;
- }
-
- TAutoClass::TExtLink::TExtLink(TClassList* list, HINSTANCE module)
- : Classes(list), Module(module), Next(0)
- {
- for (Prev = &ClassList.Link; *Prev; Prev = &(*Prev)->Next)
- ; // link to end of list
- }
-
- TAutoClass::TExtLink::~TExtLink()
- {
- *Prev = Next;
- if (Next)
- Next->Prev = Prev;
- }
-
- //____________________________________________________________________________
- //
- // TAutoStack implementation
- //____________________________________________________________________________
-
- TAutoStack::TAutoStack(VARIANT far* stack, TLocaleId locale, int argcount,
- int named, long far* map, TServedObject* owner)
- :
- Owner(owner), Stack((TAutoVal far*)stack), LangId(LANGIDFROMLCID(locale)),
- ArgCount(argcount), NamedCount(named), NamedIds(map), CurrentArg(-1)
- {
- }
-
- TAutoStack::~TAutoStack()
- {
- TAutoVal far* val;
- for (val = Stack; ArgCount; --*const_cast<int*>(&ArgCount), val++)
- val->Restore();
- }
-
- TAutoVal& TAutoStack::operator [](int index)
- {
- if (index >= ArgSymbolCount /* && index != TAutoStack::SetValue */)
- throw TXAuto(TXAuto::xNoArgSymbol);
- TAutoSymbol* argSymbol = index >= 0 ? Symbol + index + 1 : Symbol;
- int vIndex = ArgCount - index - 1; // index if not a named argument
- if (index == TAutoStack::SetValue || // property value to set
- index >= ArgCount-NamedCount) { // named or out of range
- for (vIndex = NamedCount; --vIndex >= 0; )
- if (NamedIds[vIndex] == index) {
- break;
- }
- }
- TAutoVal far* val;
- if (vIndex >= 0) {
- CurrentArg = vIndex; // save index for error return
- val = Stack[vIndex].DereferenceVariant();
- val->SetLocale(LangId);
- if (val->GetDataType() == atString &&
- argSymbol->IsEnum() &&
- argSymbol->GetEnum()->Convert(*val, Default))
- return Default;
- }
- else if (index == TAutoStack::SetValue) {
- throw TXAuto(TXAuto::xParameterMissing);
- }
- else {
- val = &Default;
- val->SetLocale(LangId);
- const char* dfltStr = argSymbol->Doc.Translate(LangId); // load default
- if (!dfltStr)
- throw TXAuto(TXAuto::xNoDefaultValue);
- Default = dfltStr; // makes a BSTR in order to use OLE conversions
- }
- return *val;
- }
-
- //____________________________________________________________________________
- //
- // Dynamic casting routines implementation
- //____________________________________________________________________________
-
- // temporary defines for using typeinfo with dynamic cast
-
- void far* __cdecl __DynamicCast(void far* object, void far* vtable,
- void far* srctyp, void far* dsttyp,
- int reference = 0);
- struct tpid {int s; short m; short n; int VptrOffs; int Flags;}; // partial
- #define CF_HAS_FARVPTR 0x1000
-
- const void far*
- DynamicCast(const void far* obj, const typeinfo& src, const typeinfo& dst)
- {
- int vtblOff;
- if (!obj)
- return obj;
- else if ((vtblOff = src.tpp->VptrOffs) == -1)
- return src==dst ? obj : 0;
- else if (src.tpp->Flags & CF_HAS_FARVPTR)
- return __DynamicCast(const_cast<void far*>(obj),
- *(void far**)((char*)obj+vtblOff), src.tpp, dst.tpp);
- else
- return __DynamicCast(const_cast<void far*>(obj),
- *(void near**)((char*)obj+vtblOff), src.tpp,dst.tpp);
- }
-
- const void far*
- MostDerived(const void far* obj, const typeinfo& src)
- {
- int vtblOff;
- if (!obj || (vtblOff = src.tpp->VptrOffs) == -1)
- return obj;
-
- else if (src.tpp->Flags & CF_HAS_FARVPTR)
- return __DynamicCast(const_cast<void far*>(obj),
- *(void far**)((char*)obj+vtblOff), src.tpp, 0);
- else
- return __DynamicCast(const_cast<void far*>(obj),
- *(void near**)((char*)obj+vtblOff), src.tpp, 0);
- }
-
- //____________________________________________________________________________
- //
- // TAutoCommand implementation - inlined to allow definition of _AUTOCLASS
- //____________________________________________________________________________
-
- TAutoCommand::TErrorMsgHook TAutoCommand_ErrorLookup = 0; // module static
-
- TAutoCommand::TCommandHook TAutoCommand_InvokeHook = 0; // module static
-
- //____________________________________________________________________________
- //
- // TAutoIterator implementation
- //____________________________________________________________________________
-
- TAutoIterator::TAutoIterator(TAutoCreator& creator,IUnknown* owner,TLangId lang)
- : Creator(creator), Owner(owner), Symbol(0), RefCnt(0), Class(0), Lang(lang) {}
- // note: RefCnt = 0 on creation, will ++ in TAutoVal operator(IUnknown*)
-
- TAutoIterator::TAutoIterator(TAutoIterator& copy)
- :
- Symbol(copy.Symbol), Owner(copy.Owner), Class(copy.Class), Lang(copy.Lang),
- Creator(copy.Creator), RefCnt(1)
- {
- Owner->AddRef();
- }
-
- TAutoIterator::~TAutoIterator()
- {
- Owner->Release();
- }
-
- //
- // IEnumVARIANT implementation
- //
-
- HRESULT _IFUNC
- TAutoIterator::QueryInterface(const GUID far& iid, void far*far* pif)
- {
- if (iid!=IID_IUnknown && iid!=IID_IEnumVARIANT) {
- *pif = 0;
- return HR_NOINTERFACE;
- }
- *pif = this;
- ++RefCnt;
- return HR_NOERROR;
- }
- unsigned long _IFUNC TAutoIterator::AddRef()
- {
- return ++RefCnt;
- }
- unsigned long _IFUNC TAutoIterator::Release()
- {
- return --RefCnt==0 ? delete this,0 : RefCnt;
- }
-
- HRESULT _IFUNC TAutoIterator::Next(unsigned long count, VARIANT far* retvals,
- unsigned long far* retcount)
- {
- unsigned long index = 0;
- try {
- while(index < count) {
- if (!Test())
- break;
- TAutoVal far& retval = ((TAutoVal far*)retvals)[(int)index];
- Return(retval);
- if (Symbol->IsEnum())
- Symbol->GetEnum()->Convert(retval, Lang);
- TObjectDescriptor objDesc;
- if (retval.GetObjDesc(objDesc)) {
- if (!objDesc.Object) // null pointer returned from function
- retval = TAutoVoid(); // return an empty value if no object
- else
- retval = Creator.CreateDispatch(objDesc);
- }
- Step();
- index++;
- }
- }
- catch(...) {
- }
- if (retcount)
- *retcount = index;
- return index==count ? HR_NOERROR : HR_FALSE;
- }
-
- HRESULT _IFUNC TAutoIterator::Skip(unsigned long count)
- {
- while(count--) {
- if (!Test())
- return HR_FALSE;
- Step();
- }
- return HR_NOERROR;
- }
-
- HRESULT _IFUNC TAutoIterator::Reset()
- {
- Init();
- return HR_NOERROR;
- }
-
- HRESULT _IFUNC TAutoIterator::Clone(IEnumVARIANT* FAR* ppenum)
- {
- try {
- *ppenum = Copy();
- return HR_NOERROR;
- }
- catch (...) {
- return HR_OUTOFMEMORY;
- }
- }
-
- //____________________________________________________________________________
- //
- // TDispatch implementation
- //____________________________________________________________________________
-
- TUnknown* TDispatchCreator::CreateObject(TObjectDescriptor objDesc,
- IUnknown* outer)
- {
- return new TDispatch(objDesc, outer);
- }
-
- IDispatch* TDispatchCreator::CreateDispatch(TObjectDescriptor objDesc,
- IUnknown* outer)
- {
- TDispatch* obj = new TDispatch(objDesc, outer);
- return *obj;
- }
-
- DEFINE_QI_OLEBASE(IDispatch, 0x20400L)
-
- DEFINE_COMBASES1(TDispatchCOM, IDispatch)
-
- TDispatch::TDispatch(TObjectDescriptor& objDesc, IUnknown* outer)
- :
- Object(const_cast<void*>(objDesc.Object)), Class(objDesc.Class)
- {
- SetOuter(outer ? outer
- : &objDesc.Class->Aggregate(const_cast<void*>(objDesc.Object), *this));
- } // note: RefCnt = 0 on creation, will ++ in TAutoVal operator(IDispatch*)
-
- // IDispatch implementation
-
- HRESULT _IFUNC
- TDispatch::GetTypeInfoCount(unsigned int far* pctinfo)
- {
- *pctinfo = 0;
- return HR_NOERROR;
- }
-
- HRESULT _IFUNC
- TDispatch::GetTypeInfo(unsigned int, LCID, ITypeInfo* far*)
- {
- return HR_NOTIMPL;
- }
-
- HRESULT _IFUNC
- TDispatch::GetIDsOfNames(const IID far& riid, OLECHAR far* far* names,
- unsigned int cNames, LCID lcid, DISPID far* dispIds)
- {
- if (riid != IID_NULL)
- return HR_DISP_UNKNOWNINTERFACE;
-
- HRESULT retval = HR_NOERROR;
- TAutoSymbol* symbol;
- for (int i = 0; i < cNames; i++) {
- dispIds[i] = -1;
- if (i == 0) {
- symbol = Class->Lookup(OleStr(names[0]), LANGIDFROMLCID(lcid),
- asAnyCommand, dispIds[0]);
- if (!symbol)
- retval = HR_DISP_UNKNOWNNAME;
- }
- else if (symbol) {
- if (!Class->LookupArg(OleStr(names[i]), LANGIDFROMLCID(lcid), symbol, dispIds[i]))
- retval = HR_DISP_UNKNOWNNAME;
- }
- }
- return retval;
- }
-
- HRESULT _IFUNC
- TDispatch::Invoke(DISPID dispidMember, const IID far& /*riid*/, LCID lcid,
- unsigned short wFlags, DISPPARAMS far* dispparams,
- VARIANT far* varResult, EXCEPINFO far* exceptInfo,
- unsigned int far* retArgErr)
- {
- ObjectPtr object = Object; // make copy in case of this pointer adjustment
- if (!object) // check if C++ object still exists
- return HR_DISP_MEMBERNOTFOUND;
- TAutoStack stack(dispparams->rgvarg, lcid, dispparams->cArgs,
- dispparams->cNamedArgs, dispparams->rgdispidNamedArgs, 0);
- stack.Symbol = Class->FindId((short)dispidMember, object);
- if (!stack.Symbol)
- return HR_DISP_MEMBERNOTFOUND;
- if (!stack.Symbol->TestFlag(wFlags)) // check attr bits for supported type
- return HR_DISP_MEMBERNOTFOUND;
- if (wFlags & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF))
- varResult = 0;
- if ((stack.ArgSymbolCount=Class->GetArgCount(*stack.Symbol)) +
- ((wFlags&(DISPATCH_PROPERTYPUT|DISPATCH_PROPERTYPUTREF))!=0) < stack.ArgCount)
- return HR_DISP_BADPARAMCOUNT;
-
- switch(Class->Dispatch(object, TDispatchCreator(), *this, wFlags, stack,
- (TAutoVal far*)varResult)) {
- case TXAuto::xNoError:
- return HR_NOERROR;
-
- case TXAuto::xNotIDispatch:
- case TXAuto::xForeignIDispatch:
- return HR_DISP_BADVARTYPE;
-
- case TXAuto::xValidateFailure:
- *retArgErr = stack.CurrentArg;
- return HR_DISP_OVERFLOW;
-
- case TXAuto::xConversionFailure:
- case TXAuto::xTypeMismatch:
- *retArgErr = stack.CurrentArg;
- return HR_DISP_TYPEMISMATCH;
-
- case TXAuto::xNoArgSymbol:
- *retArgErr = stack.CurrentArg;
- return HR_DISP_PARAMNOTFOUND;
-
- case TXAuto::xParameterMissing:
- case TXAuto::xNoDefaultValue:
- return HR_DISP_PARAMNOTOPTIONAL;
-
- case TXAuto::xErrorStatus:
- if (exceptInfo) {
- exceptInfo->wCode = (unsigned short)stack.ErrorCode;
- exceptInfo->wReserved = 0;
- exceptInfo->bstrSource = 0; //::SysAllocString(AppDesc.GetAppName(LANGIDFROMLCID(lcid)));
- exceptInfo->bstrDescription = ::SysAllocString(stack.ErrorMsg);
- exceptInfo->bstrHelpFile = 0;
- exceptInfo->pfnDeferredFillIn = 0;
- exceptInfo->scode = E_FAIL; // how to get better code? matters?
- }
- return HR_DISP_EXCEPTION;
-
- case TXAuto::xExecutionFailure:
- default:
- return HR_DISP_OVERFLOW; // no appropriate error for other
- }
- }
-
-
-