phonebookengines/VirtualPhonebook/VPbkEng/src/CVPbkPhoneNumberMatchStrategy.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 02 Feb 2010 10:12:17 +0200
changeset 0 e686773b3f54
child 35 4ae315f230bc
permissions -rw-r--r--
Revision: 201003 Kit: 201005

/*
* Copyright (c) 2005-2007 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  A high level class for matching phone numbers from stores.
*
*/


// INCLUDES
#include <CVPbkPhoneNumberMatchStrategy.h>

#include <CVPbkContactManager.h>
#include <CVPbkContactLinkArray.h>
#include <MVPbkContactOperation.h>
#include <MVPbkContactStoreList.h>
#include <MVPbkContactStore.h>
#include <MVPbkContactStoreProperties.h>
#include <MVPbkContactLink.h>
#include <MVPbkStoreContact.h>
#include <MVPbkSingleContactOperationObserver.h>
#include <RLocalizedResourceFile.h>
#include <VPbkDataCaging.hrh>
#include <VPbkEng.rsg>
#include <VPbkFieldTypeSelectors.rsg>
#include <CVPbkFieldTypeSelector.h>
#include <CVPbkFieldFilter.h>
#include <barsread.h>
#include <MVPbkContactFieldTextData.h>
#include <CVPbkContactStoreUriArray.h>

#include "CVPbkPhoneNumberSequentialMatchStrategy.h"
#include "CVPbkPhoneNumberParallelMatchStrategy.h"

// CONSTANTS
const TInt KMagicNumber = -1;

NONSHARABLE_CLASS(CVPbkPhoneNumberMatchStrategyImpl) :
        public CActive,
        public MVPbkContactFindObserver,
        public MVPbkSingleContactOperationObserver
    {
    public: // Construction
        static CVPbkPhoneNumberMatchStrategyImpl* NewL(
                CVPbkPhoneNumberMatchStrategy& aParent,
                const CVPbkPhoneNumberMatchStrategy::TConfig& aConfig,
                CVPbkContactManager& aContactManager,
                MVPbkContactFindObserver& aObserver);
        ~CVPbkPhoneNumberMatchStrategyImpl();

    public: // Interface
        void MatchL(const TDesC& aPhoneNumber);
        TInt MaxMatchDigits() const;
        TArray<MVPbkContactStore*> StoresToMatch() const;

    private: // From CActive
        void RunL();
        void DoCancel();
        TInt RunError(TInt aError);

    private: // From MVPbkContactFindObserver
        void FindCompleteL(MVPbkContactLinkArray* aResults);
        void FindFailed(TInt aError);

    private: // From MVPbkSingleContactOperationObserver
        void VPbkSingleContactOperationComplete(
                MVPbkContactOperationBase& aOperation,
                MVPbkStoreContact* aContact);
        void VPbkSingleContactOperationFailed(
                MVPbkContactOperationBase& aOperation,
                TInt aError);

    private: // Implementation
        CVPbkPhoneNumberMatchStrategyImpl(CVPbkPhoneNumberMatchStrategy& aParent);
        void ConstructL(
                const CVPbkPhoneNumberMatchStrategy::TConfig& aConfig,
                CVPbkContactManager& aContactManager,
                MVPbkContactFindObserver& aObserver);

        /**
         * Searches for a store that has given URI from the list of
         * stores that the contact manager has.
         * @param aUriPtr URI of the store to search for.
         * @return The store with aUriPtr, or NULL if store was not found.
         */
        MVPbkContactStore* FindStoreL(
                const TVPbkContactStoreUriPtr& aUriPtr);

        /**
         * Issues new request to be handled in RunL.
         */
        void IssueRequest();

        MVPbkContactLink* IsValidResultLC(MVPbkStoreContact* aContact);

        /**
         * Creates name tokens array
         * @param aContact which is checking, RPointerArray reference
         */
        void CreateNameTokensArrayL( MVPbkStoreContact* aContact, RPointerArray <HBufC>& aNameTokensArray );
        
        /**
         * Check if contact already exists in results array
         * @param aContact which is checking
         * @return True if contact exist or if array was empty.
         */
        TBool CheckContactDuplicationL( MVPbkStoreContact* aContact );
        
        /**
         * Gets contact's field value
         * @param aContact to get field's value from
         * @param aFieldType: EVPbkVersitNameN for Last Name 
         * EVPbkVersitNameFN for First Name
         * @return Pointer descriptor with field's value.
         */
        TPtrC NameFieldValueL( MVPbkStoreContact* aContact, TVPbkFieldTypeName aFieldType );
        
    private: // Data
        CVPbkPhoneNumberMatchStrategy& iParent;
        /// Ref: The contact manager instance to be used for searching.
        CVPbkContactManager* iContactManager;
        /// Ref: Observer for the searching process.
        MVPbkContactFindObserver* iObserver;
        /// Own: The find operation that is currently ongoing.
        MVPbkContactOperationBase* iOperation;
        /// Maximum number of matched digits.
        TInt iMaxMatchDigits;
        /// Flags to configure matching process, @see TVPbkPhoneNumberMatchFlags
        TUint32 iMatchFlags;

        /// Own: Array of stores that are used in matching.
        RPointerArray<MVPbkContactStore> iStoresToMatch;
        /// Own: Phone number that is being matched.
        HBufC* iPhoneNumber;
        /// Own: Intermediate results of the matching process.
        CVPbkContactLinkArray* iWorkingResults;
        /// Own: Final results of the matching process.
        CVPbkContactLinkArray* iResults;
        /// iWorkingResults index of current contact retrieval
        TInt iCurrentContact;

        /// Own: Currently retrieved contact
        MVPbkStoreContact* iStoreContact;
        /// Own: Field type selector
        CVPbkFieldTypeSelector* iFieldTypeSelector;
        /// Own: A filtered and sorted collection of Virtual Phonebook contact fields.
        CVPbkFieldFilter* iFieldFilter;

        /// Active object states
        enum TState { EMatch, ERemoveDuplicates, ERefineSearch, EComplete };
        /// Active object current state
        TState iState;
        
        /// Own: First Name field type selector
        CVPbkFieldTypeSelector* iFirstNameSelector;
        /// Own: Last Name field type selector
        CVPbkFieldTypeSelector* iLastNameSelector;
        /// Own: Array of tokens gotten from first and last name fields of first matched contact
        RPointerArray <HBufC> iNameTokensArray;
        /// Own: Array of tokens gotten from first and last name fields of contact
        RPointerArray <HBufC> iTempNameTokensArray;
        /// Indicates if all contact in iResult are the same
        TBool iDoubledContacts;
    };

CVPbkPhoneNumberMatchStrategyImpl::CVPbkPhoneNumberMatchStrategyImpl(
        CVPbkPhoneNumberMatchStrategy& aParent) :
    CActive(CActive::EPriorityIdle),
    iParent(aParent)
    {
    CActiveScheduler::Add(this);
    }

inline void CVPbkPhoneNumberMatchStrategyImpl::ConstructL(
        const CVPbkPhoneNumberMatchStrategy::TConfig& aConfig,
        CVPbkContactManager& aContactManager,
        MVPbkContactFindObserver& aObserver)
    {
    iContactManager = &aContactManager;
    iObserver = &aObserver;

    iMaxMatchDigits = aConfig.iMaxMatchDigits;
    iMatchFlags = aConfig.iMatchFlags;

    const TInt uriCount = aConfig.iUriPriorities.Count();
    for (TInt i = 0; i < uriCount; ++i)
        {
        MVPbkContactStore* store = FindStoreL(aConfig.iUriPriorities[i]);
        if (store)
            {
            iStoresToMatch.AppendL(store);
            }
        }

    VPbkEngUtils::RLocalizedResourceFile resFile;
    resFile.OpenLC(iContactManager->FsSession(),
                   KVPbkRomFileDrive,
                   KDC_RESOURCE_FILES_DIR,
                   KVPbkFieldTypeSelectorsResFileName);
    HBufC8* selectorBuf = resFile.AllocReadLC(R_VPBK_PHONE_NUMBER_SELECTOR);
    TResourceReader resReader;
    resReader.SetBuffer(selectorBuf);

    iFieldTypeSelector = CVPbkFieldTypeSelector::NewL(resReader, iContactManager->FieldTypes());

    CleanupStack::PopAndDestroy( selectorBuf );
    
    if ( iMatchFlags & CVPbkPhoneNumberMatchStrategy::EVPbkDuplicatedContactsMatchFlag )
    	{ 
	    HBufC8* firstNameSelectorBuf = resFile.AllocReadLC( R_VPBK_FIRST_NAME_SELECTOR );
	    resReader.SetBuffer( firstNameSelectorBuf );
	    iFirstNameSelector = CVPbkFieldTypeSelector::NewL( resReader, iContactManager->FieldTypes() );
	    CleanupStack::PopAndDestroy( firstNameSelectorBuf );
	
	    HBufC8* lastNameSelectorBuf = resFile.AllocReadLC( R_VPBK_LAST_NAME_SELECTOR );
	    resReader.SetBuffer( lastNameSelectorBuf );
	    iLastNameSelector = CVPbkFieldTypeSelector::NewL( resReader, iContactManager->FieldTypes() );
	    CleanupStack::PopAndDestroy( lastNameSelectorBuf );
    	}
    
    CleanupStack::PopAndDestroy( &resFile );
    }

CVPbkPhoneNumberMatchStrategyImpl* CVPbkPhoneNumberMatchStrategyImpl::NewL(
        CVPbkPhoneNumberMatchStrategy& aParent,
        const CVPbkPhoneNumberMatchStrategy::TConfig& aConfig,
        CVPbkContactManager& aContactManager,
        MVPbkContactFindObserver& aObserver)
    {
    CVPbkPhoneNumberMatchStrategyImpl* self =
            new(ELeave) CVPbkPhoneNumberMatchStrategyImpl(aParent);
    CleanupStack::PushL(self);
    self->ConstructL(aConfig, aContactManager, aObserver);
    CleanupStack::Pop(self);
    return self;
    }

CVPbkPhoneNumberMatchStrategyImpl::~CVPbkPhoneNumberMatchStrategyImpl()
    {
    Cancel();
    
    delete iFieldFilter;
    delete iFieldTypeSelector;
    delete iWorkingResults;
    delete iResults;
    delete iStoreContact;
    delete iPhoneNumber;
    delete iOperation;
    delete iFirstNameSelector;
    delete iLastNameSelector;
    iNameTokensArray.ResetAndDestroy();
    iTempNameTokensArray.ResetAndDestroy();
    iStoresToMatch.Close();
    }

void CVPbkPhoneNumberMatchStrategyImpl::MatchL(const TDesC& aPhoneNumber)
    {
    HBufC* phoneNumber = aPhoneNumber.AllocL();
    delete iPhoneNumber;
    iPhoneNumber = phoneNumber;
    
    if ( iWorkingResults )
        {
        iWorkingResults->ResetAndDestroy();
        }                
    if ( iResults )
        {
        iResults->ResetAndDestroy();
        }                
    iCurrentContact = KMagicNumber;
    
    if ( iMatchFlags & CVPbkPhoneNumberMatchStrategy::EVPbkDuplicatedContactsMatchFlag )
    	{
    	iDoubledContacts = ETrue;
    	}
    else
    	{
    	iDoubledContacts = EFalse;
    	}
    iState = EMatch;
    IssueRequest();
    }


void CVPbkPhoneNumberMatchStrategyImpl::IssueRequest()
    {
    TRequestStatus* status = &iStatus;
    User::RequestComplete(status, KErrNone);
    SetActive();
    }

void CVPbkPhoneNumberMatchStrategyImpl::RunL()
    {
    switch (iState)
        {
        case EMatch:
            {
            MVPbkContactOperation* operation = iParent.CreateFindOperationLC(*iPhoneNumber);
            if (operation)
                {
                operation->StartL();
                CleanupStack::Pop(); // operation
                delete iOperation;
                iOperation = operation;
                }
            else
                {
                // all needed operations are done if any
                iState = ERemoveDuplicates;
                IssueRequest();
                }
            break;
            }
        case ERemoveDuplicates:
            {
            const TInt count = iWorkingResults ? iWorkingResults->Count() : 0;
            CVPbkContactLinkArray* results = CVPbkContactLinkArray::NewLC();
            for (TInt i = 0; i < count; ++i)
                {
                MVPbkContactLink* link = iWorkingResults->At(i).CloneLC();
                if (results->Find(*link) == KErrNotFound)
                    {
                    results->AppendL(link);
                    CleanupStack::Pop(); // link
                    }
                else
                    {
                    CleanupStack::PopAndDestroy(); // link
                    }
                }
            CleanupStack::Pop(results);
            delete iWorkingResults;
            iWorkingResults = results;
            iState = ERefineSearch;
            iCurrentContact = 0;
            IssueRequest();
            break;
            }
        case ERefineSearch:
            {
            if (!iResults)
                {
                iResults = CVPbkContactLinkArray::NewL();
                }
            if ( iDoubledContacts )
                {
                iDoubledContacts = CheckContactDuplicationL( iStoreContact );
                }
            
            MVPbkContactLink* result = IsValidResultLC( iStoreContact );
            if ( result )
                {
                iResults->AppendL( result );
                CleanupStack::Pop(); // MVPbkContactLink
                }
            delete iStoreContact;
            iStoreContact = NULL;
                
            const TInt count = iWorkingResults ? iWorkingResults->Count() : 0;
            if (iCurrentContact < count &&                 
                !( iResults->Count() > 0 && 
                        (iMatchFlags & CVPbkPhoneNumberMatchStrategy::EVPbkStopOnFirstMatchFlag)))
                {
                const MVPbkContactLink& link = iWorkingResults->At(iCurrentContact);
                delete iOperation;
                iOperation = NULL;
                iOperation = iContactManager->RetrieveContactL(link, *this);
                ++iCurrentContact;
                }
            else
                {
                iState = EComplete;
                IssueRequest();
                }
            break;
            }
        case EComplete:
            {
            iNameTokensArray.ResetAndDestroy();
            iTempNameTokensArray.ResetAndDestroy();
            if ( iDoubledContacts && iResults->Count() )
                {
                CVPbkContactLinkArray* results = CVPbkContactLinkArray::NewLC();
                MVPbkContactLink* link = iResults->At(0).CloneLC(); // first element
                results->AppendL( link );
                CleanupStack::Pop( 2, results ); // results, link
                delete iResults;
                iResults = results;
                }
            CVPbkContactLinkArray* results = iResults;
            iResults = NULL;            
            iObserver->FindCompleteL( results );
            break;
            }
        default:
            {
            // This case should not be possible
            User::Leave( KErrArgument );
            break;
            }
        }
    }

void CVPbkPhoneNumberMatchStrategyImpl::DoCancel()
    {
    }

TInt CVPbkPhoneNumberMatchStrategyImpl::RunError(TInt aError)
    {
    iObserver->FindFailed(aError);
    return KErrNone;
    }

inline MVPbkContactStore* CVPbkPhoneNumberMatchStrategyImpl::FindStoreL(
        const TVPbkContactStoreUriPtr& aUriPtr)
    {
    const TInt storeCount = iContactManager->ContactStoresL().Count();
    for (TInt i = 0; i < storeCount; ++i)
        {
        MVPbkContactStore& store = iContactManager->ContactStoresL().At(i);
        if (store.StoreProperties().Uri().Compare(
                    aUriPtr,
                    TVPbkContactStoreUriPtr::EContactStoreUriAllComponents) == 0)
            {
            return &store;
            }
        }
    return NULL;
    }

void CVPbkPhoneNumberMatchStrategyImpl::FindCompleteL(MVPbkContactLinkArray* aResults)
    {
    // This function called by operation which is created in 
    // iParent.CreateFindOperationLC
    if (aResults)
        {
        CleanupDeletePushL( aResults ); // Take ownership
        
        if (!iWorkingResults)
            {
            iWorkingResults = CVPbkContactLinkArray::NewL();
            }
        const TInt count = aResults->Count();
        for (TInt i = 0; i < count; ++i)
            {
            MVPbkContactLink* link = aResults->At(i).CloneLC();
            iWorkingResults->AppendL(link);
            CleanupStack::Pop(); // link
            }

        CleanupStack::PopAndDestroy(); // aResults
        }
    IssueRequest();
    }

void CVPbkPhoneNumberMatchStrategyImpl::FindFailed(TInt aError)
    {
    iObserver->FindFailed(aError);
    }

void CVPbkPhoneNumberMatchStrategyImpl::VPbkSingleContactOperationComplete(
        MVPbkContactOperationBase& aOperation,
        MVPbkStoreContact* aContact)
    {
    if (iOperation == &aOperation)
        {
        delete iOperation;
        iOperation = NULL;

        iStoreContact = aContact;
        IssueRequest();
        }
    }

void CVPbkPhoneNumberMatchStrategyImpl::VPbkSingleContactOperationFailed(
        MVPbkContactOperationBase& aOperation,
        TInt aError)
    {
    if (iOperation == &aOperation)
        {
        delete iOperation;
        iOperation = NULL;

        iObserver->FindFailed(aError);
        }
    }

TInt CVPbkPhoneNumberMatchStrategyImpl::MaxMatchDigits() const
    {
    return iMaxMatchDigits;
    }

TArray<MVPbkContactStore*> CVPbkPhoneNumberMatchStrategyImpl::StoresToMatch() const
    {
    return iStoresToMatch.Array();
    }

MVPbkContactLink* CVPbkPhoneNumberMatchStrategyImpl::IsValidResultLC(
        MVPbkStoreContact* aContact)
    {
    MVPbkContactLink* result = NULL;

    if (!(iMatchFlags & CVPbkPhoneNumberMatchStrategy::EVPbkExactMatchFlag))
        {
        // If exact match is not needed we will accept the contact as valid
        // if it is not NULL
        if ( aContact )
            {
            result = aContact->CreateLinkLC();
            }        
        }
    else if (aContact)
        {
        TPtrC matchedNumber = iPhoneNumber->Des().
                Right(Min(iPhoneNumber->Length(), iMaxMatchDigits));

        CVPbkFieldFilter::TConfig config(aContact->Fields(), iFieldTypeSelector);
        if (!iFieldFilter)
            {
            iFieldFilter = CVPbkFieldFilter::NewL(config);
            }
        else
            {
            iFieldFilter->ResetL(config);
            }
        const TInt count = iFieldFilter->FieldCount();
        for (TInt i = 0; i < count; ++i)
            {
            MVPbkContactFieldData& data = iFieldFilter->FieldAt(i).FieldData();
            if (data.DataType() == EVPbkFieldStorageTypeText)
                {
                const TDesC& dataText = MVPbkContactFieldTextData::Cast(data).Text();
                TPtrC dataTextPtr = dataText.Right(Min(dataText.Length(), iMaxMatchDigits));
                if (dataTextPtr == matchedNumber)
                    {
                    result = iFieldFilter->FieldAt(i).CreateLinkLC();
                    break;
                    }
                }
            }
        }
    return result;
    }

TBool CVPbkPhoneNumberMatchStrategyImpl::CheckContactDuplicationL(
        MVPbkStoreContact* aContact )
    {

    if ( !aContact )
        {
        return ETrue;
        }
    
    if ( aContact && iCurrentContact == 1 )
        {
        CreateNameTokensArrayL( aContact, iNameTokensArray );
        return ETrue;
        }
    else 
        {
        iTempNameTokensArray.ResetAndDestroy();
        CreateNameTokensArrayL( aContact, iTempNameTokensArray );
        TInt count = iNameTokensArray.Count();
        if ( iNameTokensArray.Count() != iTempNameTokensArray.Count() )
        	{
        	return EFalse;
        	}
        else
        	{
        	TInt result = 0;
        	for ( TInt i = 0; i < count; i++ )
        		{
        		HBufC* token = iNameTokensArray[i];
        		TInt tempCount = iTempNameTokensArray.Count();
            	for ( TInt j = 0; j < tempCount; j++ )
            		{
            		result = token->Compare( *( iTempNameTokensArray[j] ) );
                	if ( !result )
                		{
                		HBufC* removedToken = iTempNameTokensArray[j];
                		iTempNameTokensArray.Remove( j );
                		delete removedToken;
                		break;
                		}
            		}
            	if ( result )
            		{
            		return EFalse;
            		}
        		}
        	}
        return ETrue;
        }
    }

void CVPbkPhoneNumberMatchStrategyImpl::CreateNameTokensArrayL( MVPbkStoreContact* aContact,
										RPointerArray <HBufC>& aNameTokensArray )
	{
    TPtrC firstName = NameFieldValueL( aContact, EVPbkVersitNameFN );
    TLex lexer;
    lexer.Assign( firstName );
    
    TPtrC ptr;
    TInt len = 0;
    while( ETrue )
        {
        ptr.Set( lexer.NextToken() );
        
        len = ptr.Length();
        if ( len )
            {
            HBufC* token = ptr.AllocLC();
            aNameTokensArray.AppendL( token );
            CleanupStack::Pop( token );
            }
        else
            {
            break;
            }
        } 

    TPtrC lastName = NameFieldValueL( aContact, EVPbkVersitNameN );

    lexer.Assign( lastName );
    
    len = 0;
    while( ETrue )
        {
        ptr.Set( lexer.NextToken() );
        
        len = ptr.Length();
        if ( len )
            {
            HBufC* token = ptr.AllocLC();
            aNameTokensArray.AppendL( token );
            CleanupStack::Pop( token );
            }
        else
            {
            break;
            }
        }
	}

TPtrC CVPbkPhoneNumberMatchStrategyImpl::NameFieldValueL( 
        MVPbkStoreContact* aContact, TVPbkFieldTypeName aFieldType )
    {
    if ( aContact )
        {
        CVPbkFieldFilter::TConfig config( aContact->Fields() );
        if ( aFieldType == EVPbkVersitNameFN )
            {
            config.iFieldSelector = iFirstNameSelector;
            }
        else if ( aFieldType == EVPbkVersitNameN )
            {
            config.iFieldSelector = iLastNameSelector;
            }
        else
            {
            return KNullDesC();
            }
        
        if (!iFieldFilter)
            {
            iFieldFilter = CVPbkFieldFilter::NewL(config);
            }
        else
            {
            iFieldFilter->ResetL(config);
            }
        const TInt count = iFieldFilter->FieldCount();
        for (TInt i = 0; i < count; ++i)
            {
            MVPbkContactFieldData& data = iFieldFilter->FieldAt(i).FieldData();
            if (data.DataType() == EVPbkFieldStorageTypeText)
                {
                return  MVPbkContactFieldTextData::Cast(data).Text();
                }
            }
        return KNullDesC();
        }
    else
        {
        return KNullDesC();
        }
    }

CVPbkPhoneNumberMatchStrategy::CVPbkPhoneNumberMatchStrategy()
    {
    }

EXPORT_C CVPbkPhoneNumberMatchStrategy* CVPbkPhoneNumberMatchStrategy::NewL(
        const TConfig& aConfig,
        CVPbkContactManager& aContactManager,
        MVPbkContactFindObserver& aObserver)
    {
    if (aConfig.iMatchMode == EVPbkSequentialMatch)
        {
        return CVPbkPhoneNumberSequentialMatchStrategy::NewL(aConfig, aContactManager, aObserver);
        }
    else
        {
        return CVPbkPhoneNumberParallelMatchStrategy::NewL(aConfig, aContactManager, aObserver);
        }
   }

CVPbkPhoneNumberMatchStrategy::~CVPbkPhoneNumberMatchStrategy()
    {
    delete iImpl;
    }

EXPORT_C void CVPbkPhoneNumberMatchStrategy::MatchL(const TDesC& aPhoneNumber)
    {
    InitMatchingL();

    iImpl->MatchL(aPhoneNumber);
    }

void CVPbkPhoneNumberMatchStrategy::BaseConstructL(
        const TConfig& aConfig,
        CVPbkContactManager& aContactManager,
        MVPbkContactFindObserver& aObserver)
    {
    iImpl = CVPbkPhoneNumberMatchStrategyImpl::NewL(
            *this, aConfig, aContactManager, aObserver);
    }

MVPbkContactFindObserver& CVPbkPhoneNumberMatchStrategy::FindObserver() const
    {
    return *iImpl;
    }

TInt CVPbkPhoneNumberMatchStrategy::MaxMatchDigits() const
    {
    return iImpl->MaxMatchDigits();
    }

TArray<MVPbkContactStore*> CVPbkPhoneNumberMatchStrategy::StoresToMatch() const
    {
    return iImpl->StoresToMatch();
    }

// End of File