qtcontactsmobility/plugins/contacts/symbian/src/filtering/cntfilterdetail.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Wed, 23 Jun 2010 18:02:44 +0300
changeset 46 efe85016a067
parent 40 b46a585f6909
permissions -rw-r--r--
Revision: 201023 Kit: 2010125

/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <centralrepository.h>
#include <cntfldst.h>

#include "cntfilterdetail.h"
#include "cntfilterdetaildisplaylabel.h" //todo rename class to follow naming pattern CntFilterDetailDisplayLabel
#include "cntsqlsearch.h"
#include "cntsymbianengine.h"
#include "cnttransformphonenumber.h"

// Telephony Configuration API
// Keys under this category are used in defining telephony configuration.
const TUid KCRUidTelConfiguration = {0x102828B8};
// Amount of digits to be used in contact matching.
// This allows a customer to variate the amount of digits to be matched.
const TUint32 KTelMatchDigits                               = 0x00000001;
// Default match length
const TInt KDefaultMatchLength(7);

CntFilterDetail::CntFilterDetail(CContactDatabase& contactDatabase,CntSymbianSrvConnection &cntServer,CntDbInfo& dbInfo) 
                                        : m_contactdatabase(contactDatabase),
                                          m_srvConnection(cntServer),
                                          m_dbInfo(dbInfo),
                                          m_emulateBestMatching(false)
{
}

CntFilterDetail::~CntFilterDetail()
{
}


QList<QContactLocalId> CntFilterDetail::contacts(
        const QContactFilter &filter,
        const QList<QContactSortOrder> &sortOrders,
        bool &filterSupportedflag,
        QContactManager::Error* error)
{
    Q_UNUSED(filterSupportedflag);
    //Check if any invalid filter is passed 
    if (!filterSupported(filter) ) {
        *error =  QContactManager::NotSupportedError;
        return QList<QContactLocalId>();
    }
    QList<QContactLocalId> idList;
    QContactDetailFilter detailFilter(filter);
    QString sqlQuery;
    //Check for phonenumber. Special handling needed
    if ( (detailFilter.detailDefinitionName() == QContactPhoneNumber::DefinitionName ) &&
            (detailFilter.detailFieldName() != QContactPhoneNumber::FieldSubTypes)) {
        //Handle phonenumber ...
        createMatchPhoneNumberQuery(filter,sqlQuery,error);
        if (*error == QContactManager::NoError) {
            //fetch the contacts
            idList =  m_srvConnection.searchContacts(sqlQuery,error);
        }
    }

    else if (detailFilter.matchFlags() == QContactFilter::MatchKeypadCollation) {
        //predictive search filter
        idList = HandlePredictiveSearchFilter(filter,error);
    }
            
    // handle other cases
    else {
        createSelectQuery(filter,sqlQuery,error);
        QString sortQuery = m_dbInfo.getSortQuery(sortOrders, sqlQuery, error);
        
        if (*error == QContactManager::NoError) {
            //fetch the contacts
            idList =  m_srvConnection.searchContacts(sortQuery, error);
        }
    }
    return idList;
}

bool CntFilterDetail::filterSupported(const QContactFilter& filter) 
{
    bool result = false;
    if (QContactFilter::ContactDetailFilter == filter.type()) {
        result = true;
    }
    return result;
}

void CntFilterDetail::createSelectQuery(const QContactFilter& filter,
                                        QString& sqlQuery,
                                        QContactManager::Error* error)

{
    if (!filterSupported(filter)) {
      *error = QContactManager::NotSupportedError;
      return;
    }
    QContactDetailFilter detailFilter(filter);
    //display label
    if (detailFilter.detailDefinitionName() == QContactDisplayLabel::DefinitionName) {
      CntFilterDetailDisplayLabel displayLabelFilter;
      displayLabelFilter.createSelectQuery(filter, sqlQuery, error);
    }
    //type
    else if (detailFilter.detailDefinitionName() == QContactType::DefinitionName) {
       if (detailFilter.value().toString() == QContactType::TypeContact)
           sqlQuery = "SELECT contact_id FROM contact WHERE (type_flags>>24)=0";
       else if (detailFilter.value().toString() == QContactType::TypeGroup)
           sqlQuery = "SELECT contact_id FROM contact WHERE (type_flags>>24)=3";
    }
    else if (detailFilter.detailDefinitionName() == QContactGuid::DefinitionName) {
       if (detailFilter.detailFieldName() == QContactGuid::FieldGuid) {
           QStringList fullGuidValue = detailFilter.value().toString().split('-');
           if (fullGuidValue.count() == 3) {
               QString localGuidValue = fullGuidValue.at(1);
               sqlQuery = "SELECT contact_id FROM contact WHERE guid_string = '" + localGuidValue + '\'';
           }
       }
    }
    //everything else
    else {   
       QString tableName;
       QString sqlWhereClause;
       getTableNameWhereClause(detailFilter,tableName,sqlWhereClause,error);
       //Create the sql query
       sqlQuery += "SELECT DISTINCT contact_id FROM " + tableName + " WHERE " + sqlWhereClause;
    }
}

/*!
 * Updates match flags for columns.
 */
void CntFilterDetail::updateForMatchFlag(const QContactDetailFilter& filter,
                                         QString& fieldToUpdate ,
                                         QContactManager::Error* error) const
{
    // Modify the filed depending on the query
    switch (filter.matchFlags()) {
        case QContactFilter::MatchExactly: {
            // Pattern for MatchExactly:
            // " ='xyz'"
            fieldToUpdate = " ='"
                           + filter.value().toString() + '\'';
            *error = QContactManager::NoError;
            break;
        }
        case QContactFilter::MatchContains: {
            // Pattern for MatchContains:
            // " LIKE '%xyz%'"
            fieldToUpdate = " LIKE '%" + filter.value().toString() + "%'" ;
            *error = QContactManager::NoError;
            break;
        }
        case QContactFilter::MatchStartsWith: {
            // Pattern for MatchStartsWith:
            // " LIKE 'xyz%'"
            fieldToUpdate = " LIKE '" +  filter.value().toString() + "%'"  ;
            *error = QContactManager::NoError;
            break;
        }
        case QContactFilter::MatchEndsWith: {
            // Pattern for MatchEndsWith:
            // " LIKE '%xyz'"
            fieldToUpdate = " LIKE '%" + filter.value().toString() + '\'' ;
            *error = QContactManager::NoError;
            break;
        }
        case QContactFilter::MatchFixedString: {
            *error = QContactManager::NotSupportedError;
            break;
        }
        case QContactFilter::MatchCaseSensitive: {
            *error = QContactManager::NotSupportedError;
            break;
        }
        default: {
            *error = QContactManager::NotSupportedError;
            break;
        }
    }
}

void CntFilterDetail::getTableNameWhereClause(const QContactDetailFilter& detailfilter,
                                              QString& tableName,
                                              QString& sqlWhereClause ,
                                              QContactManager::Error* error) const
{
    //Get the table name and the column name
    QString columnName;
    bool isSubType;

    m_dbInfo.getDbTableAndColumnName(detailfilter.detailDefinitionName(), detailfilter.detailFieldName(), tableName, columnName, isSubType);

    // return if tableName is empty
    if (tableName.isEmpty()) {
        *error = QContactManager::NotSupportedError;
        return;
    }

    //check columnName
    if (columnName.isEmpty()) {
        *error = QContactManager::NotSupportedError;
        return;
    }
    else if (isSubType) {
        sqlWhereClause += columnName;
        sqlWhereClause += " NOT NULL ";
    }
    else {
        sqlWhereClause += ' ' + columnName + ' ';
        QString fieldToUpdate;
        //Update the value depending on the match flag
        updateForMatchFlag(detailfilter,fieldToUpdate,error);
        sqlWhereClause +=  fieldToUpdate;
    }
}

QList<QContactLocalId>  CntFilterDetail::HandlePredictiveSearchFilter(const QContactFilter& filter,
                                                                      QContactManager::Error* error)
{
    if (filter.type() == QContactFilter::ContactDetailFilter) {
        const QContactDetailFilter detailFilter(filter);
        if (detailFilter.matchFlags() == QContactFilter::MatchKeypadCollation) {
            QString pattern = detailFilter.value().toString();
            if ( detailFilter.detailFieldName() == QContactEmailAddress::FieldEmailAddress ) {
                return  m_srvConnection.searchOnServer(
                        pattern, CntSymbianSrvConnection::CntPredictiveSearchList, error);
            } else {
                QString sqlQuery;
                CntSqlSearch sqlSearch;
                //convert string to numeric format
                sqlQuery = sqlSearch.CreatePredictiveSearch(pattern);
                return  m_srvConnection.searchContacts(sqlQuery, error);
            }
        }
    }
    return QList<QContactLocalId>();
}


/*
 * Creates an sql query to fetch contact item IDs for all the contact items
 * which may contain the specified telephone number in a telephone, fax
 * or SMS type field.
 *
 * The comparison method used is not exact.  The number is compared starting from
 * the right side of the number and the method returns an array of candidate
 * matches.  Punctuation (e.g. spaces) and other alphabetic characters are ignored
 * when comparing.
 * 
 * Note that due to the way numbers are stored in the database, it is recommended
 * that at least 7 match digits are specified even when matching a number
 * containing fewer digits.  Failure to follow this guideline may (depending on the
 * database contents) mean that the function will not return the expected Contact
 * IDs.
 */
void CntFilterDetail::createMatchPhoneNumberQuery(
                                      const QContactFilter& filter,
                                      QString& sqlQuery,
                                      QContactManager::Error* error)

{
    if (!filterSupported(filter) ) {
      *error = QContactManager::NotSupportedError;
      return;
    }
          
    QContactDetailFilter detailFilter(filter);
    QString number((detailFilter.value()).toString());
    TPtrC numberPtr(reinterpret_cast<const TUint16*>(number.utf16()));
    
    TInt matchLengthFromRight(KDefaultMatchLength);
    // no need to propagate error, we can use the default match length
    TRAP_IGNORE(getMatchLengthL(matchLengthFromRight));
    
    TInt numLowerDigits = matchLengthFromRight;
    TInt numUpperDigits = 0;
    
    if (numLowerDigits > KLowerSevenDigits) {
        numLowerDigits = KLowerSevenDigits;
        numUpperDigits = matchLengthFromRight - KLowerSevenDigits;
    }
    else if (numLowerDigits == 0) {
        // best match phonenumbers
        numLowerDigits = KLowerSevenDigits;
    }
    
    TMatch phoneDigits = createPaddedPhoneDigits(
                          numberPtr, numLowerDigits, numUpperDigits, error);
    
    if (*error == QContactManager::NoError) {
        // select fields for contacts that match phone lookup
        //  SELECT contact_id FROM comm_addr
        //      WHERE value = [value string] AND type = [type value];
        //
        QString type =  QString(" type = %1").arg(CntDbInfo::EPhoneNumber);
        QString value =  QString(" value = %1").arg(phoneDigits.iLowerSevenDigits);
        QString whereClause = " WHERE" + value + " AND" + type;
        if (matchLengthFromRight <= KLowerSevenDigits) {
            // Matching 7 or less digits...
            sqlQuery = "SELECT contact_id FROM comm_addr" + whereClause;
        }
        else {
            // Checking the upper digits...
            TMatch phoneNumber = createPhoneMatchNumber(
                                  numberPtr, numLowerDigits, numUpperDigits, error);
            QString fieldToMatch = QString(" LIKE '%1").arg(phoneNumber.iUpperDigits) + "%'"  ;
            whereClause += " AND extra_value" + fieldToMatch;
            sqlQuery = "SELECT contact_id FROM comm_addr" + whereClause;
        }
      
        // refine search
        if (bestMatchingEnabled()) {
            QList<QContactLocalId> list =  m_srvConnection.searchContacts(sqlQuery,error);
            QList<QContactLocalId> bestMatchingIds;
            if (*error == QContactManager::NoError) {
                TRAP_IGNORE(
                        bestMatchingIds = getBestMatchPhoneNumbersL(number, list, error);
                )
                if (bestMatchingIds.count()>0) {
                    // recreate query
                    QString selectQuery = " SELECT contact_id FROM comm_addr WHERE contact_id in (";
                    QString ids = QString("%1").arg(bestMatchingIds.at(0));
                    for(int i=1; i<bestMatchingIds.count(); ++i) {
                        ids += QString(" ,%1").arg(bestMatchingIds.at(i));
                    }
                    selectQuery += ids + ')';
                    sqlQuery = selectQuery;
                }
                else {
                    // empty list
                    QString selectQuery = " SELECT contact_id FROM comm_addr WHERE contact_id in (null)";
                    sqlQuery = selectQuery;
                }
            }
        }
    }
}

#ifdef PBK_UNIT_TEST
void CntFilterDetail::emulateBestMatching()
{
    m_emulateBestMatching = true;
}
#endif

/*
 * Best matching number if matchLengthFromRight set to 0
 */
bool CntFilterDetail::bestMatchingEnabled() 
{
#ifdef PBK_UNIT_TEST
    if (m_emulateBestMatching) {
        return true;
    }
#endif
    bool result = false;
    TInt matchLengthFromRight(KDefaultMatchLength);
    TRAP_IGNORE(getMatchLengthL(matchLengthFromRight));
    if (matchLengthFromRight == 0) {
        result = true;
    }
    return result;
}

/*
 * Get the match length setting. Digits to be used in matching (counted from
 * right).
 */
bool CntFilterDetail::getMatchLengthL(TInt& matchLength)
{
#ifdef PBK_UNIT_TEST
    if (m_emulateBestMatching) {
        matchLength = 0;
        return true;
    }
#endif
    //Get number of digits used to match
    bool result = false;
    CRepository* repository = CRepository::NewL(KCRUidTelConfiguration);
    TInt err = repository->Get(KTelMatchDigits, matchLength);
    delete repository;
    result = (err == KErrNone);
    return result;
}

/*
 * Convert the supplied string to a matchable phone number.
 *
 * \param text Descriptor containing phone number.
 * \param lowerMatchlength Number of least significant phone digits to use.
 * \param upperMatchLength Number of most significant phone digits to use.
 * \param error Qt error code.
 * \return The hash code(s) to use when matching the supplied phone number.
 */
CntFilterDetail::TMatch CntFilterDetail::createPaddedPhoneDigits(
                                            const TDesC& number, 
                                            const TInt numLowerDigits, 
                                            const TInt numUpperDigits,
                                            QContactManager::Error* error)
{
    TMatch phoneNumber = createPhoneMatchNumber(
                                            number, numLowerDigits, numUpperDigits, error);
    if (*error == QContactManager::NoError) {
        if (phoneNumber.iNumLowerDigits + phoneNumber.iUpperDigits == 0) {
            // No digits, do nothing
        }
        else if (phoneNumber.iNumLowerDigits < KLowerSevenDigits) {
            // Only the lower-digits hash is used, pad out the number to
            // KLowerSevenDigits.
            TInt pad = KLowerSevenDigits - phoneNumber.iNumLowerDigits;
            phoneNumber.iLowerSevenDigits = TMatch::padOutPhoneMatchNumber(phoneNumber.iLowerSevenDigits,pad);
        }
        else if (phoneNumber.iNumUpperDigits < (KMaxPhoneMatchLength - KLowerSevenDigits) ) {
            // The lower-digits hash is full, pad out the upper hash if less than 15
            // digits total.
            TInt pad = KMaxPhoneMatchLength - KLowerSevenDigits - phoneNumber.iNumUpperDigits;
            phoneNumber.iUpperDigits = TMatch::padOutPhoneMatchNumber(phoneNumber.iUpperDigits,pad);
        }
    }
    return phoneNumber;
}

/*
 * Returns the hash code(s) to use when matching the supplied phone number.  If the
 * number supplied has more actual phone digits (i.e. not including spaces) than
 * KLowerSevenDigits, a second hash is generated to hold the remaining most
 * significant phone digits. Removes the non-digit characters.

 * \param text Descriptor containing contacts phone number field.
 * \param lowerMatchlength Number of least significant phone digits to use.
 * \param upperMatchLength Number of most significant phone digits to use.
 * \param error Qt error code.
 * \return The hash code(s) to use when matching the supplied phone number.
 */
CntFilterDetail::TMatch CntFilterDetail::createPhoneMatchNumber(
                                            const TDesC& text, 
                                            TInt lowerMatchLength, 
                                            TInt upperMatchLength,
                                            QContactManager::Error* error)
{
    const TInt KBufLength = KCntMaxTextFieldLength+1;
    TBuf<KBufLength> buf;
    
    if (text.Length() <= KBufLength) {
        buf = text;
    }
    else {
        buf = text.Right(KBufLength);
    }
    TMatch::stripOutNonDigitChars(buf);
    
    TMatch phoneNumber;
    if (buf.Length() == 0) {
        *error = QContactManager::BadArgumentError;
        return phoneNumber;
    }
    
    // Generate a hash for the upper digits only if the phone number string is
    // large enough and more than 7 digits are to be matched.
    TInt phoneNumberlength = buf.Length();
    if ((phoneNumberlength > KLowerSevenDigits) && (upperMatchLength > 0)) {
        TPtrC upperPart = buf.Left(phoneNumberlength - KLowerSevenDigits);
        phoneNumber.iUpperDigits = TMatch::createHash(upperPart,
            upperMatchLength, phoneNumber.iNumUpperDigits);
    }
    // Generate a hash of the lower digits.
    phoneNumber.iLowerSevenDigits = TMatch::createHash(buf, 
            lowerMatchLength, phoneNumber.iNumLowerDigits);
    
    return phoneNumber;
}

QList<QContactLocalId> CntFilterDetail::getBestMatchPhoneNumbersL(
                                      const QString number,
                                      const QList<QContactLocalId>& idList,
                                      QContactManager::Error* error)

{
    TPtrC numberPtr(reinterpret_cast<const TUint16*>(number.utf16()));
    RBuf matchNumber;
    matchNumber.CleanupClosePushL();
    matchNumber.CreateL(numberPtr);
  
    QList<QContactLocalId> bestMatchingIds;
    for (int i=0; i<idList.count(); i++) {
        QContact contact = m_dbInfo.engine()->contact(idList.at(i), QContactFetchHint(), error);
        QList<QContactPhoneNumber> details = contact.details<QContactPhoneNumber>();
        CntTransformContactData* transformPhoneNumber = new CntTransformPhoneNumber();
        
        bool matchFound(false);
        for (int j = 0;j < details.count(); j++) {
            QList<CContactItemField *> fields = transformPhoneNumber->transformDetailL(details.at(j));
            for (int k = 0;k < details.count() && !matchFound; k++) {
                CContactTextField* storage = fields.at(k)->TextStorage();
                RBuf phoneNumber;
                phoneNumber.CleanupClosePushL();
                phoneNumber.CreateL(storage->Text());
                if (TMatch::validateBestMatchingRulesL(phoneNumber,matchNumber)) {
                    matchFound = true;
                }
                // phoneNumber
                CleanupStack::PopAndDestroy();
            }
            if (matchFound) {
                bestMatchingIds.append(idList.at(i));
                break;
            }
        }
        delete transformPhoneNumber;
    }
    // matchNumber
    CleanupStack::PopAndDestroy();
    return bestMatchingIds;
}

//CntFilterDetail::TMatch constructor.
CntFilterDetail::TMatch::TMatch()
    :
    iLowerSevenDigits(0),
    iUpperDigits(0),
    iNumLowerDigits(0),
    iNumUpperDigits(0)
{
}

/*
 * Generates a hash value by reversing the matchLength least significant digits,
 * ignoring non-digits and zeroes at the end of the number.  Returns error if no phone
 * digits are supplied.

 * \param phoneNumberString A descriptor containing a phone number.
 * \param matchLength The number of digits from the right of the phone number to use.
 * \param numPhoneDigits The number of digits found in the phone number string.
 * \param error Qt error code.*
 * \return An integer representation of the phone number string in reverse.
 */
TInt32 CntFilterDetail::TMatch::createHash(
                                    const TDesC& phoneNumberString, 
                                    TInt matchLength, 
                                    TInt& numPhoneDigits)
{
    TInt phoneNumberLength = phoneNumberString.Length();
    TInt startIndex = 0;
    if (phoneNumberLength > matchLength) {
        startIndex = phoneNumberLength - matchLength;
    }
    
    numPhoneDigits = 0;
    TUint reversedDigits = 0;
    TInt mult = 1;
    
    for (TInt chrIndex = startIndex; (numPhoneDigits < matchLength) && (chrIndex < phoneNumberLength); chrIndex++) {
        TChar chr = phoneNumberString[chrIndex];
        if (chr.IsDigit()) {
            reversedDigits += (chr.GetNumericValue()) * mult;
            mult = mult * 10;
            ++numPhoneDigits;
        }
    }
    return reversedDigits ;
}

void CntFilterDetail::TMatch::stripOutNonDigitChars(TDes& text)
{
    for (TInt chrPos = 0; chrPos < text.Length(); ++chrPos) {
        TChar chr = text[chrPos];
        if (!chr.IsDigit()) {
            text.Delete(chrPos, 1);
            --chrPos;
        }
    }
}

TInt32 CntFilterDetail::TMatch::padOutPhoneMatchNumber(TInt32& phoneNumber,
                                                       TInt padOutLength)
{
    TInt32 result(phoneNumber);
    const TInt KMult(10);
    for (TInt i = 0; i < padOutLength; ++i) {
        result *= KMult;
    }
    phoneNumber = result;
    return result;
}

// Removes non-digit chars except plus form the beginning
// Checks if number matches to one of defined types
//
TInt CntFilterDetail::TMatch::formatAndCheckNumberType(TDes& number)
    {
    _LIT( KOneZeroPattern, "0*" );
    _LIT( KTwoZerosPattern, "00*" );
    _LIT( KPlusPattern, "+*" );
    const TChar KPlus = TChar('+');
    const TChar KZero = TChar('0');
    const TChar KAsterisk = TChar('*');
    const TChar KHash = TChar('#');
    
    for( TInt pos = 0; pos < number.Length(); ++pos ) {
        TChar chr = number[pos];
        if ( !chr.IsDigit() && !( pos == 0 && chr == KPlus  )
                && !( chr == KAsterisk ) && !( chr == KHash ) ) {
            number.Delete( pos, 1 );
            --pos;
        }
    }
    
    TInt format;
    
    if (!number.Match(KTwoZerosPattern) && number.Length() > 2 && number[2] != KZero) {
        format = ETwoZeros;
    }
    else if (!number.Match(KOneZeroPattern)&& number.Length() > 1 && number[1] != KZero) {
        format = EOneZero;
    }
    else if (!number.Match(KPlusPattern) && number.Length() > 1 && number[1] != KZero) {
        format = EPlus;
    }
    else if (number.Length() > 0 && number[0] != KZero && ( ( TChar ) number[0] ).IsDigit()) {
        format = EDigit;
    }
    else {
        format = EUnknown;
    }

    return format;
    }

TBool CntFilterDetail::TMatch::validateBestMatchingRulesL(const TDesC& phoneNumber, const TDesC& matchNumber)
    {
    RBuf numberA;
    numberA.CleanupClosePushL();
    numberA.CreateL(matchNumber);
    TNumberType numberAType = (TNumberType) TMatch::formatAndCheckNumberType(numberA);
    
    RBuf numberB;
    numberB.CleanupClosePushL();
    numberB.CreateL(phoneNumber);
    TNumberType numberBType = (TNumberType) TMatch::formatAndCheckNumberType(numberB);

    TBool match = (!numberB.Compare(numberA) ||
                    TMatch::checkBestMatchingRules(numberA, numberAType, numberB, numberBType) ||
                    TMatch::checkBestMatchingRules(numberB, numberBType, numberA, numberAType));
    
    // cleanup
    CleanupStack::PopAndDestroy(2);
    return match;
    }

TBool CntFilterDetail::TMatch::checkBestMatchingRules(
        const TDesC& numberA, TNumberType numberAType,
        const TDesC& numberB, TNumberType numberBType  )
    {
    TBool result = EFalse;
    
    // Rules for matching not identical numbers
    // Rules details are presented in Best_Number_Matching_Algorithm_Description.doc
    
    // rule International-International 1
    if (!result && numberAType == EPlus && numberBType == ETwoZeros) {
        TPtrC numberAPtr = numberA.Right(numberA.Length() - 1);
        TPtrC numberBPtr = numberB.Right(numberB.Length() - 2);
        result = !(numberAPtr.Compare(numberBPtr));
        if (result) {
            return result;
        }
    }

    // rule International-International 2
    if (numberAType == EPlus && numberBType == EDigit) {
        TPtrC numberAPtr = numberA.Right( numberA.Length() - 1 );
        if (numberAPtr.Length() < numberB.Length()) {
            TPtrC numberBPtr = numberB.Right( numberAPtr.Length() );
            result = !(numberAPtr.Compare(numberBPtr));
            if (result) {
                return result;
            }
        }
    }

    // rule International-International 3
    if (numberAType == ETwoZeros && numberBType == EDigit) {
        TPtrC numberAPtr = numberA.Right(numberA.Length() - 2);
        if (numberAPtr.Length() < numberB.Length()) {
            TPtrC numberBPtr = numberB.Right(numberAPtr.Length());
            result = !(numberAPtr.Compare(numberBPtr));
            if (result) {
                return result;
            }
        }
    }

    // rule International-Operator 1
    if (numberAType == EOneZero && numberBType == EPlus
            || numberAType == EDigit && numberBType == EPlus) {
        TPtrC numberAPtr;
        if (numberAType == EOneZero) {
            numberAPtr.Set(numberA.Right(numberA.Length() - 1));
        }
        else {
            numberAPtr.Set(numberA);
        }
        if (numberAPtr.Length() < numberB.Length() - 1) {
            TPtrC numberBPtr = numberB.Right(numberAPtr.Length());
            result = !(numberAPtr.Compare(numberBPtr));
            if (result) {
                return result;
            }
        }
    }

    // rule International-Operator 2
    if (numberAType == EOneZero && numberBType == ETwoZeros
            || numberAType == EDigit && numberBType == ETwoZeros) {
        TPtrC numberAPtr;
        if (numberAType == EOneZero) {
            numberAPtr.Set(numberA.Right(numberA.Length() - 1));
        }
        else {
            numberAPtr.Set(numberA);
        }
        if (numberAPtr.Length() < numberB.Length() - 2) {
            TPtrC numberBPtr = numberB.Right(numberAPtr.Length());
            result = !(numberAPtr.Compare(numberBPtr));
            if (result) {
                return result;
            }
        }
    }

    // rule International-Operator 3
    if (numberAType == EOneZero && numberBType == EDigit
            || numberAType == EDigit && numberBType == EDigit) {
        TPtrC numberAPtr;
        if (numberAType == EOneZero) {
            numberAPtr.Set(numberA.Right( numberA.Length() - 1));
        }
        else {
            numberAPtr.Set(numberA);
        }
        if (numberAPtr.Length() < numberB.Length()) {
            TPtrC numberBPtr = numberB.Right(numberAPtr.Length());
            result = !(numberAPtr.Compare(numberBPtr));
            if (result) {
                return result;
            }
        }
    }

    // rule Operator-Operator 1
    if (numberAType == EOneZero && numberBType == EDigit) {
        TPtrC numberAPtr = numberA.Right(numberA.Length() - 1);
        result = !(numberAPtr.Compare(numberB));
        if (result) {
            return result;
        }
    }
    
    // rule North America Numbering Plan 1
    if (numberAType == EDigit && numberBType == EPlus) {
        TPtrC numberBPtr = numberB.Right(numberB.Length() - 1);
        result = !(numberA.Compare(numberBPtr));
        if (result) {
            return result;
        }
    }

    // More exceptional acceptance rules can be added here
    // Keep rules updated in the document Best_Number_Matching_Algorithm_Description.doc

    return result;
    }