/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
 
#include <string.h>
 
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <unordered_map>
 
#include <libxml/xmlwriter.h>
 
#include <sal/log.hxx>
#include <svl/itemset.hxx>
#include <svl/itempool.hxx>
#include <svl/itemiter.hxx>
#include <svl/setitem.hxx>
#include <svl/whiter.hxx>
#include <svl/voiditem.hxx>
 
#ifdef DBG_UTIL
static size_t nAllocatedSfxItemSetCount(0);
static size_t nUsedSfxItemSetCount(0);
static size_t nAllocatedSfxPoolItemHolderCount(0);
static size_t nUsedSfxPoolItemHolderCount(0);
size_t getAllocatedSfxItemSetCount() { return nAllocatedSfxItemSetCount; }
size_t getUsedSfxItemSetCount() { return nUsedSfxItemSetCount; }
size_t getAllocatedSfxPoolItemHolderCount() { return nAllocatedSfxPoolItemHolderCount; }
size_t getUsedSfxPoolItemHolderCount() { return nUsedSfxPoolItemHolderCount; }
 
// <TotalCount, <number of entries, sum of used count>>
typedef std::unordered_map<sal_uInt16, std::pair<sal_uInt32, sal_uInt32>> ItemArrayUsage;
static ItemArrayUsage aItemArrayUsage;
 
static void addArrayUsage(sal_uInt16 nCount, sal_uInt16 nTotalCount)
{
    ItemArrayUsage::iterator aHit(aItemArrayUsage.find(nTotalCount));
    if (aHit == aItemArrayUsage.end())
    {
        aItemArrayUsage.insert({nTotalCount, {1, nCount}});
        return;
    }
    aHit->second.first++;
    aHit->second.second += nCount;
}
 
SVL_DLLPUBLIC void listSfxItemSetUsage()
{
    struct sorted {
        sal_uInt16 nTotalCount;
        sal_uInt32 nAppearances;
        sal_uInt32 nAllUsedCount;
        sorted(sal_uInt16 _nTotalCount, sal_uInt32 _nAppearances, sal_uInt32 _nAllUsedCount)
            : nTotalCount(_nTotalCount), nAppearances(_nAppearances), nAllUsedCount(_nAllUsedCount) {}
        bool operator<(const sorted& rDesc) const { return nTotalCount > rDesc.nTotalCount; }
    };
    std::vector<sorted> aSorted;
    aSorted.reserve(aItemArrayUsage.size());
    for (const auto& rEntry : aItemArrayUsage)
        aSorted.emplace_back(rEntry.first, rEntry.second.first, rEntry.second.second);
    std::sort(aSorted.begin(), aSorted.end());
    SAL_INFO("svl.items", "ITEM: List of " << aItemArrayUsage.size() << " SfxItemPool TotalCounts with usages:");
    double fAllFillRatePercent(0.0);
    sal_uInt32 nUsed(0);
    sal_uInt32 nAllocated(0);
    for (const auto& rEntry : aSorted)
    {
        const sal_uInt32 nAllCount(rEntry.nAppearances * rEntry.nTotalCount);
        const double fFillRatePercent(0 == nAllCount ? 0.0 : (static_cast<double>(rEntry.nAllUsedCount) / static_cast<double>(nAllCount)) * 100.0);
        SAL_INFO("svl.items",
            " TotalCount: " << rEntry.nTotalCount
            << " Appearances: " << rEntry.nAppearances
            << " FillRate(%): " << fFillRatePercent);
        fAllFillRatePercent += fFillRatePercent;
        nUsed += rEntry.nAllUsedCount;
        nAllocated += rEntry.nTotalCount * rEntry.nAppearances;
    }
    SAL_INFO("svl.items", " Average FillRate(%): " << fAllFillRatePercent / aItemArrayUsage.size());
    SAL_INFO("svl.items", " Used: " << nUsed << " Allocated: " << nAllocated);
    SAL_INFO("svl.items", " Average Used/Allocated(%): " << (static_cast<double>(nUsed) / static_cast<double>(nAllocated)) * 100.0);
}
#endif
// NOTE: Only needed for one Item in SC (see notes below for
// ScPatternAttr). Still keep it so that when errors
// come up to this change be able to quickly check using the
// fallback flag 'ITEM_CLASSIC_MODE'
 
// I thought about this constructor a while, but when there is no
// Item we need no cleanup at destruction (what we would need the
// Pool for), so it is OK and makes default construction easier
// when no Pool is needed. The other constructors guarantee that
// there *cannot* be a state with Item set and Pool not set. IF
// you change this class, ALWAYS ensure that this can not happen (!)
SfxPoolItemHolder::SfxPoolItemHolder()
: m_pPool(nullptr)
, m_pItem(nullptr)
#ifndef NDEBUG
, m_bDeleted(false)
#endif
{
#ifdef DBG_UTIL
    nAllocatedSfxPoolItemHolderCount++;
    nUsedSfxPoolItemHolderCount++;
#endif
}
 
SfxPoolItemHolder::SfxPoolItemHolder(SfxItemPool& rPool, const SfxPoolItem* pItem, bool bPassingOwnership)
: m_pPool(&rPool)
, m_pItem(pItem)
#ifndef NDEBUG
, m_bDeleted(false)
#endif
{
#ifdef DBG_UTIL
    nAllocatedSfxPoolItemHolderCount++;
    nUsedSfxPoolItemHolderCount++;
#endif
    if (nullptr != m_pItem)
        m_pItem = implCreateItemEntry(getPool(), m_pItem, bPassingOwnership);
    if (nullptr != m_pItem && getPool().NeedsSurrogateSupport(m_pItem->Which()))
        getPool().registerPoolItemHolder(*this);
}
 
SfxPoolItemHolder::SfxPoolItemHolder(const SfxPoolItemHolder& rHolder)
: m_pPool(rHolder.m_pPool)
, m_pItem(rHolder.m_pItem)
#ifndef NDEBUG
, m_bDeleted(false)
#endif
{
#ifdef DBG_UTIL
    assert(!rHolder.isDeleted() && "Destructed instance used (!)");
    nAllocatedSfxPoolItemHolderCount++;
    nUsedSfxPoolItemHolderCount++;
#endif
    if (nullptr != m_pItem)
        m_pItem = implCreateItemEntry(getPool(), m_pItem, false);
    if (nullptr != m_pItem && getPool().NeedsSurrogateSupport(m_pItem->Which()))
        getPool().registerPoolItemHolder(*this);
}
 
SfxPoolItemHolder::~SfxPoolItemHolder()
{
#ifdef DBG_UTIL
    assert(!isDeleted() && "Destructed instance used (!)");
    nAllocatedSfxPoolItemHolderCount--;
#endif
    if (nullptr != m_pItem && getPool().NeedsSurrogateSupport(m_pItem->Which()))
        getPool().unregisterPoolItemHolder(*this);
    if (nullptr != m_pItem)
        implCleanupItemEntry(m_pItem);
#ifndef NDEBUG
    m_bDeleted = true;
#endif
}
 
const SfxPoolItemHolder& SfxPoolItemHolder::operator=(const SfxPoolItemHolder& rHolder)
{
    assert(!isDeleted() && "Destructed instance used (!)");
    assert(!rHolder.isDeleted() && "Destructed instance used (!)");
    if (this == &rHolder || *this == rHolder)
        return *this;
 
    if (nullptr != m_pItem && getPool().NeedsSurrogateSupport(m_pItem->Which()))
        getPool().unregisterPoolItemHolder(*this);
 
    if (nullptr != m_pItem)
        implCleanupItemEntry(m_pItem);
 
    m_pPool = rHolder.m_pPool;
    m_pItem = rHolder.m_pItem;
 
    if (nullptr != m_pItem)
        m_pItem = implCreateItemEntry(getPool(), m_pItem, false);
 
    if (nullptr != m_pItem && getPool().NeedsSurrogateSupport(m_pItem->Which()))
        getPool().registerPoolItemHolder(*this);
 
    return *this;
}
 
bool SfxPoolItemHolder::operator==(const SfxPoolItemHolder &rHolder) const
{
    assert(!isDeleted() && "Destructed instance used (!)");
    assert(!rHolder.isDeleted() && "Destructed instance used (!)");
    return m_pPool == rHolder.m_pPool && areSfxPoolItemPtrsEqual(m_pItem, rHolder.m_pItem);
}
 
/**
 * Ctor for a SfxItemSet with exactly the Which Ranges, which are known to
 * the supplied SfxItemPool.
 *
 * For Sfx programmers: an SfxItemSet constructed in this way cannot
 * contain any Items with SlotIds as Which values.
 */
SfxItemSet::SfxItemSet(SfxItemPool& rPool)
: m_pPool(&rPool)
, m_pParent(nullptr)
, m_nRegister(0)
#ifdef DBG_UTIL
, m_nRegisteredSfxItemIter(0)
#endif
, m_aWhichRanges(rPool.GetMergedIdRanges())
, m_aPoolItemMap()
{
#ifdef DBG_UTIL
    nAllocatedSfxItemSetCount++;
    nUsedSfxItemSetCount++;
#endif
    assert(m_aWhichRanges.validRanges2());
}
 
SfxItemSet::SfxItemSet(SfxItemPool& pool, WhichRangesContainer wids)
: m_pPool(&pool)
, m_pParent(nullptr)
, m_nRegister(0)
#ifdef DBG_UTIL
, m_nRegisteredSfxItemIter(0)
#endif
, m_aWhichRanges(std::move(wids))
, m_aPoolItemMap()
{
#ifdef DBG_UTIL
    nAllocatedSfxItemSetCount++;
    nUsedSfxItemSetCount++;
#endif
    assert(m_aWhichRanges.TotalCount() != 0);
    assert(m_aWhichRanges.validRanges2());
}
 
SfxItemSet::SfxItemSet( const SfxItemSet& rASet )
: m_pPool( rASet.m_pPool )
, m_pParent( rASet.m_pParent )
, m_nRegister( 0 )
#ifdef DBG_UTIL
, m_nRegisteredSfxItemIter(0)
#endif
, m_aWhichRanges( rASet.m_aWhichRanges )
, m_aPoolItemMap()
{
#ifdef DBG_UTIL
    nAllocatedSfxItemSetCount++;
    nUsedSfxItemSetCount++;
#endif
    if (rASet.GetRanges().empty())
        return;
 
    for (const auto& rSource : rASet.m_aPoolItemMap)
    {
        const SfxPoolItem* pNew(implCreateItemEntry(*GetPool(), rSource.second, false));
        m_aPoolItemMap[rSource.first] = pNew;
        if (m_nRegister != rASet.m_nRegister)
            checkAddPoolRegistration(pNew);
    }
 
    assert(m_aWhichRanges.validRanges2());
}
 
SfxItemSet::SfxItemSet(SfxItemSet&& rASet) noexcept
: m_pPool( rASet.m_pPool )
, m_pParent( rASet.m_pParent )
, m_nRegister( rASet.m_nRegister )
#ifdef DBG_UTIL
, m_nRegisteredSfxItemIter(0)
#endif
, m_aWhichRanges( std::move(rASet.m_aWhichRanges) )
, m_aPoolItemMap( std::move(rASet.m_aPoolItemMap) )
{
#ifdef DBG_UTIL
    nAllocatedSfxItemSetCount++;
    nUsedSfxItemSetCount++;
    assert(0 == rASet.m_nRegisteredSfxItemIter && "ITEM: SfxItemSet MOVE constructor with active SfxItemIters (!)");
#endif
    // deregister if rASet is registered before ptrs vanish
    if (0 != rASet.m_nRegister)
        rASet.GetPool()->unregisterItemSet(rASet);
 
    // register if new set needs that
    if (0 != m_nRegister)
        GetPool()->registerItemSet(*this);
 
    // taking over ownership
    rASet.m_pPool = nullptr;
    rASet.m_pParent = nullptr;
    rASet.m_nRegister = 0;
    rASet.m_aWhichRanges.reset();
 
    assert(m_aWhichRanges.validRanges2());
}
 
SfxItemSet::~SfxItemSet()
{
#ifdef DBG_UTIL
    nAllocatedSfxItemSetCount--;
    addArrayUsage(Count(), TotalCount());
#endif
    // cleanup items. No std::fill needed, we are done with this ItemSet.
    // the callback is not set in destructor, so no worries about that
    ClearAllItemsImpl();
 
    // for invariant-testing
    m_aWhichRanges.reset();
}
 
 
// Delete single Items or all Items (nWhich == 0)
sal_uInt16 SfxItemSet::ClearItem( sal_uInt16 nWhich )
{
    if( !Count() )
        return 0;
 
    if( nWhich )
        return ClearSingleItem_ForWhichID(nWhich);
 
    // clear all & reset to nullptr
    return ClearAllItemsImpl();
}
 
sal_uInt16 SfxItemSet::ClearSingleItem_ForWhichID( sal_uInt16 nWhich )
{
    PoolItemMap::iterator aHit(m_aPoolItemMap.find(nWhich));
 
    if (aHit == m_aPoolItemMap.end())
        return 0;
 
#ifdef DBG_UTIL
    assert(0 == m_nRegisteredSfxItemIter && "ITEM: SfxItemSet ClearItem with active SfxItemIters (!)");
#endif
 
    ClearSingleItem_PrepareRemove(aHit->second);
    m_aPoolItemMap.erase(aHit);
 
    return 1;
}
 
void SfxItemSet::ClearSingleItem_PrepareRemove(const SfxPoolItem* pItem)
{
    if (nullptr == pItem)
        return;
 
    // Notification-Callback
    Changed(pItem, nullptr);
 
    // check register for remove
    checkRemovePoolRegistration(pItem);
 
    // cleanup item & reset ptr
    implCleanupItemEntry(pItem);
}
 
void SfxItemSet::checkRemovePoolRegistration(const SfxPoolItem* pItem)
{
    if (nullptr == pItem)
        // no Item, done
        return;
 
    if (IsInvalidItem(pItem) || IsDisabledItem(pItem))
        // checks IsInvalidItem/IsDisabledItem
        return;
 
    if (SfxItemPool::IsSlot(pItem->Which()))
        // no slots, these do not support NeedsSurrogateSupport
        return;
 
    if(!GetPool()->NeedsSurrogateSupport(pItem->Which()))
        // not needed for this item, done
        return;
 
    // there must be a registered one
    assert(0 != m_nRegister);
 
    // decrement counter
    m_nRegister--;
 
    // deregister when no more Items that NeedsSurrogateSupport exist
    if (0 == m_nRegister)
        GetPool()->unregisterItemSet(*this);
 
    if (pItem->isNameOrIndex())
        GetPool()->unregisterNameOrIndex(*pItem);
}
 
void SfxItemSet::checkAddPoolRegistration(const SfxPoolItem* pItem)
{
    if (nullptr == pItem)
        // no Item, done
        return;
 
    if (IsInvalidItem(pItem) || IsDisabledItem(pItem))
        // checks IsInvalidItem/IsDisabledItem
        return;
 
    if (SfxItemPool::IsSlot(pItem->Which()))
        // no slots, these do not support NeedsSurrogateSupport
        return;
 
    if(!GetPool()->NeedsSurrogateSupport(pItem->Which()))
        // not needed for this item, done
        return;
 
    // register when first Item that NeedsSurrogateSupport exist
    if (0 == m_nRegister)
        GetPool()->registerItemSet(*this);
 
    if (pItem->isNameOrIndex())
        GetPool()->registerNameOrIndex(*pItem);
 
    // increment counter
    m_nRegister++;
}
 
sal_uInt16 SfxItemSet::ClearAllItemsImpl()
{
    if (0 == Count())
        // no items set, done
        return 0;
 
#ifdef DBG_UTIL
    assert(0 == m_nRegisteredSfxItemIter && "ITEM: SfxItemSet ClearAllItems with active SfxItemIters (!)");
#endif
 
    // loop & cleanup items
    for (const auto& rCandidate : m_aPoolItemMap)
        ClearSingleItem_PrepareRemove(rCandidate.second);
 
    // remember count before resetting it, that is the retval
    const sal_uInt16 nRetval(Count());
    m_aPoolItemMap.clear();
 
    if (0 != m_nRegister)
    {
        GetPool()->unregisterItemSet(*this);
        m_nRegister = 0;
    }
 
    return nRetval;
}
 
void SfxItemSet::ClearInvalidItems()
{
    if (0 == Count())
        // no items set, done
        return;
 
    // loop, here using const_iterator due to need to set ptr in m_ppItems array
    for (PoolItemMap::iterator aCandidate(m_aPoolItemMap.begin()); aCandidate != m_aPoolItemMap.end();)
    {
        if (IsInvalidItem(aCandidate->second))
        {
#ifdef DBG_UTIL
            assert(0 == m_nRegisteredSfxItemIter && "ITEM: SfxItemSet ClearInvalidItems with active SfxItemIters (!)");
#endif
            aCandidate = m_aPoolItemMap.erase(aCandidate);
        }
        else
            aCandidate++;
    }
}
 
SfxItemState SfxItemSet::GetItemState_ForWhichID( SfxItemState eState, sal_uInt16 nWhich, bool bSrchInParent, const SfxPoolItem **ppItem) const
{
    PoolItemMap::const_iterator aHit(m_aPoolItemMap.find(nWhich));
 
    if (aHit != m_aPoolItemMap.end())
    {
        if (IsInvalidItem(aHit->second))
            // Different ones are present
            return SfxItemState::INVALID;
 
        if (IsDisabledItem(aHit->second))
            // Item is Disabled
            return SfxItemState::DISABLED;
 
        // if we have the Item, add it to output an hand back
        if (nullptr != ppItem)
            *ppItem = aHit->second;
 
        // Item is set
        return SfxItemState::SET;
    }
 
    if (GetRanges().doesContainWhich(nWhich))
    {
        // set to Default
        eState = SfxItemState::DEFAULT;
    }
 
    // search in parent?
    if (bSrchInParent && nullptr != GetParent() && (SfxItemState::UNKNOWN == eState || SfxItemState::DEFAULT == eState))
    {
        // nOffset was only valid for *local* SfxItemSet, need to continue with WhichID
        // Use the *highest* SfxItemState as result
        return GetParent()->GetItemState_ForWhichID( eState, nWhich, true, ppItem);
    }
 
    return eState;
}
 
bool SfxItemSet::HasItem(sal_uInt16 nWhich, const SfxPoolItem** ppItem) const
{
    const bool bRet(SfxItemState::SET == GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, true, ppItem));
 
    // we need to reset ppItem when it was *not* set by GetItemState_ForWhichID
    // since many usages of that return parameter re-use it, so it might still
    // be set to 'something'
    if (!bRet && nullptr != ppItem)
    {
        *ppItem = nullptr;
    }
 
    return bRet;
}
 
void SfxItemSet::CollectHasItems(std::vector<sal_uInt16>& rItemWhichs) const
{
    for(auto const & rPair : m_aWhichRanges)
    {
        const sal_uInt16 nBeg = rPair.first;
        const sal_uInt16 nEnd = rPair.second;
        for( sal_uInt16 nWhich = nBeg; nWhich <= nEnd; ++nWhich )
        {
            bool bHasItem = false;
            auto aHit(m_aPoolItemMap.find(nWhich));
            if (aHit != m_aPoolItemMap.end())
            {
                bHasItem = !IsInvalidItem(aHit->second) && !IsDisabledItem(aHit->second);
            }
            else
            {
                if (m_pParent)
                    bHasItem = SfxItemState::SET == m_pParent->GetItemState_ForWhichID( SfxItemState::DEFAULT, nWhich, true, nullptr);
            }
            if (bHasItem)
                rItemWhichs.push_back( nWhich );
        }
    }
}
 
const SfxPoolItem* SfxItemSet::PutImplAsTargetWhich(const SfxPoolItem& rItem, sal_uInt16 nTargetWhich, bool bPassingOwnership)
{
    if (0 == nTargetWhich || nTargetWhich == rItem.Which())
        // nTargetWhich not different or not given, use default
        return PutImpl(rItem, bPassingOwnership);
 
    if (bPassingOwnership && 0 == rItem.GetRefCount())
    {
        // we *can* use rItem when it's not pooled AKA has no RefCount
        const_cast<SfxPoolItem&>(rItem).SetWhich(nTargetWhich);
        return PutImpl(rItem, true);
    }
 
    // else we have to create a clone, set WhichID at it and
    // delete rItem when bPassingOwnership was intended
    SfxPoolItem* pClone(rItem.Clone(GetPool()));
    pClone->SetWhich(nTargetWhich);
    if (bPassingOwnership)
        delete &rItem;
    return PutImpl(*pClone, true);
}
 
const SfxPoolItem* SfxItemSet::PutImpl(const SfxPoolItem& rItem, bool bPassingOwnership)
{
    if (IsDisabledItem(&rItem))
    {
        // no action needed: IsDisabledItem
        if (bPassingOwnership)
            delete &rItem;
        return nullptr;
    }
 
    const sal_uInt16 nWhich(rItem.Which());
 
    if (!GetRanges().doesContainWhich(nWhich))
    {
        // no action needed: not in WhichRange
        if (bPassingOwnership)
            delete &rItem;
        return nullptr;
    }
 
    const SfxPoolItem* pEntry(nullptr);
    PoolItemMap::iterator aHit(m_aPoolItemMap.find(nWhich));
 
    if (aHit != m_aPoolItemMap.end())
    {
        // compare items, evtl. containing content compare
        pEntry = aHit->second;
 
        if (SfxPoolItem::areSame(*pEntry, rItem))
        {
            // no action needed: identical item already in place
            if (bPassingOwnership)
                delete &rItem;
            return nullptr;
        }
    }
 
    // prepare new entry
    const SfxPoolItem* pNew(implCreateItemEntry(*GetPool(), &rItem, bPassingOwnership));
 
    // Notification-Callback
    Changed(pEntry, pNew);
 
    // check register for add/remove. add first so that unregister/register
    // is avoided when an Item is replaced (increase, decrease, do not reach 0)
    checkAddPoolRegistration(pNew);
    checkRemovePoolRegistration(pEntry);
 
    // cleanup old entry & set entry at m_ppItems array
    implCleanupItemEntry(pEntry);
 
    if (pEntry)
        aHit->second = pNew;
    else
    {
#ifdef DBG_UTIL
        assert(0 == m_nRegisteredSfxItemIter && "ITEM: SfxItemSet PutImpl with active SfxItemIters (!)");
#endif
        m_aPoolItemMap[nWhich] = pNew;
    }
 
    return pNew;
}
 
bool SfxItemSet::Put(const SfxItemSet& rSource, bool bInvalidAsDefault)
{
    if (0 == rSource.Count())
        // no items in source, done
        return false;
 
    bool bRetval(false);
 
    for (PoolItemMap::const_iterator aCandidate(rSource.m_aPoolItemMap.begin()); aCandidate != rSource.m_aPoolItemMap.end(); aCandidate++)
    {
        if (IsInvalidItem(aCandidate->second))
        {
            if (bInvalidAsDefault)
            {
                bRetval |= 0 != ClearSingleItem_ForWhichID(aCandidate->first);
            }
            else
            {
                DisableOrInvalidateItem_ForWhichID(false, aCandidate->first);
            }
        }
        else
        {
            bRetval |= nullptr != PutImpl(*aCandidate->second, false);
        }
    }
 
    return bRetval;
}
 
/**
 * This method takes the Items from the 'rSet' and adds to '*this'.
 * Which ranges in '*this' that are non-existent in 'rSet' will not
 * be altered. The Which range of '*this' is also not changed.
 *
 * Items set in 'rSet' are also set in '*this'.
 * Default (0 pointer) and Invalid (-1 pointer) Items are processed
 * according to their parameter 'eDontCareAs' and 'eDefaultAs':
 *
 * SfxItemState::SET:       Hard set to the default of the Pool
 * SfxItemState::DEFAULT:   Deleted (0 pointer)
 * SfxItemState::INVALID:  Invalid (-1 pointer)
 *
 * NB: All other values for 'eDontCareAs' and 'eDefaultAs' are invalid
 */
void SfxItemSet::PutExtended
(
    const SfxItemSet&   rSource,        // Source of the Items to be put
    SfxItemState        eDontCareAs,    // What will happen to the DontCare Items
    SfxItemState        eDefaultAs      // What will happen to the Default Items
)
{
    // don't "optimize" with "if( rSource.Count()" because of dontcare + defaults
    for (const WhichPair& rPair : rSource.GetRanges())
    {
        for (sal_uInt16 nWhich = rPair.first; nWhich <= rPair.second; nWhich++)
        {
            PoolItemMap::const_iterator aHit(rSource.m_aPoolItemMap.find(nWhich));
 
            if (aHit != rSource.m_aPoolItemMap.end())
            {
                if (IsInvalidItem(aHit->second))
                {
                    // Item is DontCare:
                    switch (eDontCareAs)
                    {
                        case SfxItemState::SET:
                            PutImpl(rSource.GetPool()->GetUserOrPoolDefaultItem(nWhich), false);
                            break;
 
                        case SfxItemState::DEFAULT:
                            ClearSingleItem_ForWhichID(nWhich);
                            break;
 
                        case SfxItemState::INVALID:
                            DisableOrInvalidateItem_ForWhichID(false, nWhich);
                            break;
 
                        default:
                            assert(!"invalid Argument for eDontCareAs");
                    }
                }
                else
                {
                    // Item is set:
                    PutImpl(*aHit->second, false);
                }
            }
            else
            {
                // Item is default:
                switch (eDefaultAs)
                {
                    case SfxItemState::SET:
                        PutImpl(rSource.GetPool()->GetUserOrPoolDefaultItem(nWhich), false);
                        break;
 
                    case SfxItemState::DEFAULT:
                        ClearSingleItem_ForWhichID(nWhich);
                        break;
 
                    case SfxItemState::INVALID:
                        DisableOrInvalidateItem_ForWhichID(false, nWhich);
                        break;
 
                    default:
                        assert(!"invalid Argument for eDefaultAs");
                }
            }
        }
    }
}
 
/**
 * Expands the ranges of settable items by 'nFrom' to 'nTo'. Keeps state of
 * items which are new ranges too.
 */
void SfxItemSet::MergeRange( sal_uInt16 nFrom, sal_uInt16 nTo )
{
    // check if all from new range are already included. This will
    // use the cache in WhichRangesContainer since we check linearly.
    // Start with assuming all are included, but only if not empty.
    // If empty all included is wrong (and GetRanges().MergeRange
    // will do the right thing/shortcut)
    bool bAllIncluded(!GetRanges().empty());
 
    for (sal_uInt16 a(nFrom); bAllIncluded && a <= nTo; a++)
        if (!GetRanges().doesContainWhich(a))
            bAllIncluded = false;
 
    // if yes, we are done
    if (bAllIncluded)
        return;
 
    // need to create new WhichRanges
    auto aNewRanges = m_aWhichRanges.MergeRange(nFrom, nTo);
    RecreateRanges_Impl(aNewRanges);
    m_aWhichRanges = std::move(aNewRanges);
}
 
/**
 * Modifies the ranges of settable items. Keeps state of items which
 * are new ranges too.
 */
void SfxItemSet::SetRanges( const WhichRangesContainer& aNewRanges )
{
    // Identical Ranges?
    if (GetRanges() == aNewRanges)
        return;
 
    assert(aNewRanges.validRanges2());
    RecreateRanges_Impl(aNewRanges);
    m_aWhichRanges = aNewRanges;
}
 
void SfxItemSet::SetRanges( WhichRangesContainer&& aNewRanges )
{
    // Identical Ranges?
    if (GetRanges() == aNewRanges)
        return;
 
    assert(aNewRanges.validRanges2());
    RecreateRanges_Impl(aNewRanges);
    m_aWhichRanges = std::move(aNewRanges);
}
 
void SfxItemSet::RecreateRanges_Impl(const WhichRangesContainer& rNewRanges)
{
    if (0 == Count())
        // no existing items, done
        return;
 
    // check if existing items are in the new ItemRanges.
    // if they are not, remove the item
    for (PoolItemMap::iterator aCandidate(m_aPoolItemMap.begin()); aCandidate != m_aPoolItemMap.end();)
    {
        if (!rNewRanges.doesContainWhich(aCandidate->first))
        {
#ifdef DBG_UTIL
            assert(0 == m_nRegisteredSfxItemIter && "ITEM: SfxItemSet RecreateRanges with active SfxItemIters (!)");
#endif
            ClearSingleItem_PrepareRemove(aCandidate->second);
            aCandidate = m_aPoolItemMap.erase(aCandidate);
        }
        else
            aCandidate++;
    }
}
 
/**
 * The SfxItemSet takes over exactly those SfxPoolItems that are
 * set in rSet and are in their own Which range. All others are removed.
 * The SfxItemPool is retained, such that SfxPoolItems that have been
 * taken over, are moved from the rSet's SfxItemPool to the SfxItemPool
 * of *this.
 *
 * SfxPoolItems in rSet, for which holds 'IsInvalidItem() == true' are
 * taken over as invalid items.
 *
 * @return bool true
 *              SfxPoolItems have been taken over
 *
 *              false
 *              No SfxPoolItems have been taken over, because
 *              e.g. the Which ranges of SfxItemSets are not intersecting
 *              or the intersection does not contain SfxPoolItems that are
 *              set in rSet
 */
bool SfxItemSet::Set
(
    const SfxItemSet&   rSet,   /*  The SfxItemSet, whose SfxPoolItems are
                                    to been taken over */
 
    bool                bDeep   /*  true (default)
 
                                    The SfxPoolItems from the parents that may
                                    be present in rSet, are also taken over into
                                    this SfxPoolItemSet
 
                                    false
                                    The SfxPoolItems from the parents of
                                    rSet are not taken into account */
)
{
    if (Count())
        ClearItem();
 
    if (!bDeep)
        return Put(rSet, false);
 
    bool bRet = false;
    SfxWhichIter aIter1(*this);
    SfxWhichIter aIter2(rSet);
    sal_uInt16 nWhich1 = aIter1.FirstWhich();
    sal_uInt16 nWhich2 = aIter2.FirstWhich();
    for (;;)
    {
        if (!nWhich1 || !nWhich2)
            break;
        if (nWhich1 > nWhich2)
        {
            nWhich2 = aIter2.NextWhich();
            continue;
        }
        if (nWhich1 < nWhich2)
        {
            nWhich1 = aIter1.NextWhich();
            continue;
        }
        const SfxPoolItem* pItem;
        if( SfxItemState::SET == aIter2.GetItemState( true, &pItem ) )
            bRet |= nullptr != Put( *pItem );
        nWhich1 = aIter1.NextWhich();
        nWhich2 = aIter2.NextWhich();
    }
 
    return bRet;
}
 
const SfxPoolItem* SfxItemSet::GetItem(sal_uInt16 nId, bool bSearchInParent) const
{
    // evtl. Convert from SlotID to WhichId
    const sal_uInt16 nWhich(GetPool()->GetWhichIDFromSlotID(nId));
 
    // Is the Item set or 'bDeep == true' available?
    const SfxPoolItem *pItem(nullptr);
    const SfxItemState eState(GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, bSearchInParent, &pItem));
 
    if (bSearchInParent && SfxItemState::DEFAULT == eState && SfxItemPool::IsWhich(nWhich))
    {
        pItem = &GetPool()->GetUserOrPoolDefaultItem(nWhich);
    }
 
    return pItem;
}
 
const SfxPoolItem& SfxItemSet::Get( sal_uInt16 nWhich, bool bSrchInParent) const
{
    PoolItemMap::const_iterator aHit(m_aPoolItemMap.find(nWhich));
 
    if (aHit != m_aPoolItemMap.end())
    {
        if (IsInvalidItem(aHit->second))
        {
            return GetPool()->GetUserOrPoolDefaultItem(nWhich);
        }
#ifdef DBG_UTIL
        if (IsDisabledItem(aHit->second))
            SAL_INFO("svl.items", "SFX_WARNING: Getting disabled Item");
#endif
        return *aHit->second;
    }
 
    if (bSrchInParent && nullptr != GetParent())
    {
        return GetParent()->Get(nWhich, bSrchInParent);
    }
 
    // Get the Default from the Pool and return
    assert(m_pPool);
    return GetPool()->GetUserOrPoolDefaultItem(nWhich);
}
 
/**
 * Notification callback
 */
void SfxItemSet::Changed(const SfxPoolItem*, const SfxPoolItem*) const
{
}
 
/**
 * Only retain the Items that are also present in rSet
 * (nevermind their value).
 */
void SfxItemSet::Intersect( const SfxItemSet& rSet )
{
    // Delete all Items not contained in rSet
    assert(m_pPool && "Not implemented without Pool");
 
    if (!Count() || this == &rSet)
        // none set -> none to delete
        // same ItemSet? -> no Items not contained
        return;
 
    if (!rSet.Count())
    {
        // no Items contained in rSet -> Delete everything
        ClearAllItemsImpl();
        return;
    }
 
    // CAUTION: In the former impl, the
    // - version for different ranges checked for SfxItemState::UNKNOWN
    //   in rSet -> this means that the WhichID is *not* defined in
    //   the ranges of rSet *at all* > definitely an *error*
    // - version for same ranges checked for
    //   nullptr != local && nullptr == rSet.
    // All together I think also using the text
    // "Delete all Items not contained in rSet" leads to
    // locally delete all Items that are *not* set in rSet
    // -> != SfxItemState::SET
 
    for (PoolItemMap::iterator aCandidate(m_aPoolItemMap.begin()); aCandidate != m_aPoolItemMap.end();)
    {
        if (SfxItemState::SET != rSet.GetItemState_ForWhichID(SfxItemState::UNKNOWN, aCandidate->first, false, nullptr))
        {
#ifdef DBG_UTIL
            assert(0 == m_nRegisteredSfxItemIter && "ITEM: SfxItemSet Intersect with active SfxItemIters (!)");
#endif
            ClearSingleItem_PrepareRemove(aCandidate->second);
            aCandidate = m_aPoolItemMap.erase(aCandidate);
        }
        else
            aCandidate++;
    }
}
 
void SfxItemSet::Differentiate(const SfxItemSet& rSet)
{
    assert(m_pPool && "Not implemented without Pool");
 
    // Delete all Items contained in rSet
    if (!Count() || !rSet.Count())
        // None set?
        return;
 
    if (this == &rSet)
    {
        // same ItemSet, all Items are contained -> Delete everything
        ClearAllItemsImpl();
        return;
    }
 
    // CAUTION: In the former impl, the
    // - version for different ranges checked for SfxItemState::SET
    //   in rSet
    // - version for same ranges checked for
    //   nullptr != local && nullptr != rSet.
    // All together I think also using the text
    // "Delete all Items contained in rSet" leads to
    // locally delete all Items that *are *not* set in rSet
    // -> ==SfxItemState::SET
 
    for (PoolItemMap::iterator aCandidate(m_aPoolItemMap.begin()); aCandidate != m_aPoolItemMap.end();)
    {
        if (SfxItemState::SET == rSet.GetItemState_ForWhichID(SfxItemState::UNKNOWN, aCandidate->first, false, nullptr))
        {
#ifdef DBG_UTIL
            assert(0 == m_nRegisteredSfxItemIter && "ITEM: SfxItemSet Differentiate with active SfxItemIters (!)");
#endif
            ClearSingleItem_PrepareRemove(aCandidate->second);
            aCandidate = m_aPoolItemMap.erase(aCandidate);
        }
        else
            aCandidate++;
    }
}
 
/**
 * Decision table for MergeValue(s)
 *
 * Principles:
 * 1. If the Which value in the 1st set is "unknown", there's never any action
 * 2. If the Which value in the 2nd set is "unknown", it's made the "default"
 * 3. For comparisons the values of the "default" Items are take into account
 *
 * 1st Item    2nd Item    Values  bIgnoreDefs       Remove      Assign       Add
 *
 * set         set         ==      sal_False           -           -           -
 * default     set         ==      sal_False           -           -           -
 * dontcare    set         ==      sal_False           -           -           -
 * unknown     set         ==      sal_False           -           -           -
 * set         default     ==      sal_False           -           -           -
 * default     default     ==      sal_False           -           -           -
 * dontcare    default     ==      sal_False           -           -           -
 * unknown     default     ==      sal_False           -           -           -
 * set         dontcare    ==      sal_False        1st Item       -1          -
 * default     dontcare    ==      sal_False           -           -1          -
 * dontcare    dontcare    ==      sal_False           -           -           -
 * unknown     dontcare    ==      sal_False           -           -           -
 * set         unknown     ==      sal_False        1st Item       -1          -
 * default     unknown     ==      sal_False           -           -           -
 * dontcare    unknown     ==      sal_False           -           -           -
 * unknown     unknown     ==      sal_False           -           -           -
 *
 * set         set         !=      sal_False        1st Item       -1          -
 * default     set         !=      sal_False           -           -1          -
 * dontcare    set         !=      sal_False           -           -           -
 * unknown     set         !=      sal_False           -           -           -
 * set         default     !=      sal_False        1st Item       -1          -
 * default     default     !=      sal_False           -           -           -
 * dontcare    default     !=      sal_False           -           -           -
 * unknown     default     !=      sal_False           -           -           -
 * set         dontcare    !=      sal_False        1st Item       -1          -
 * default     dontcare    !=      sal_False           -           -1          -
 * dontcare    dontcare    !=      sal_False           -           -           -
 * unknown     dontcare    !=      sal_False           -           -           -
 * set         unknown     !=      sal_False        1st Item       -1          -
 * default     unknown     !=      sal_False           -           -           -
 * dontcare    unknown     !=      sal_False           -           -           -
 * unknown     unknown     !=      sal_False           -           -           -
 *
 * set         set         ==      sal_True            -           -           -
 * default     set         ==      sal_True            -       2nd Item     2nd Item
 * dontcare    set         ==      sal_True            -           -           -
 * unknown     set         ==      sal_True            -           -           -
 * set         default     ==      sal_True            -           -           -
 * default     default     ==      sal_True            -           -           -
 * dontcare    default     ==      sal_True            -           -           -
 * unknown     default     ==      sal_True            -           -           -
 * set         dontcare    ==      sal_True            -           -           -
 * default     dontcare    ==      sal_True            -           -1          -
 * dontcare    dontcare    ==      sal_True            -           -           -
 * unknown     dontcare    ==      sal_True            -           -           -
 * set         unknown     ==      sal_True            -           -           -
 * default     unknown     ==      sal_True            -           -           -
 * dontcare    unknown     ==      sal_True            -           -           -
 * unknown     unknown     ==      sal_True            -           -           -
 *
 * set         set         !=      sal_True         1st Item       -1          -
 * default     set         !=      sal_True            -        2nd Item    2nd Item
 * dontcare    set         !=      sal_True            -           -           -
 * unknown     set         !=      sal_True            -           -           -
 * set         default     !=      sal_True            -           -           -
 * default     default     !=      sal_True            -           -           -
 * dontcare    default     !=      sal_True            -           -           -
 * unknown     default     !=      sal_True            -           -           -
 * set         dontcare    !=      sal_True         1st Item       -1          -
 * default     dontcare    !=      sal_True            -           -1          -
 * dontcare    dontcare    !=      sal_True            -           -           -
 * unknown     dontcare    !=      sal_True            -           -           -
 * set         unknown     !=      sal_True            -           -           -
 * default     unknown     !=      sal_True            -           -           -
 * dontcare    unknown     !=      sal_True            -           -           -
 * unknown     unknown     !=      sal_True            -           -           -
 */
 
void SfxItemSet::MergeItem_Impl(sal_uInt16 nWhich, const SfxPoolItem *pFnd2, bool bIgnoreDefaults)
{
    // callers need to ensure that nWhich is in local range
    assert(GetRanges().doesContainWhich(nWhich) && "ITEM: call to MergeItem_Impl with WhichID outside local range (!)");
    const PoolItemMap::iterator aHit(m_aPoolItemMap.find(nWhich));
 
    if (aHit == m_aPoolItemMap.end())
    {
        // 1st Item nWhich is not set (Default)
        const SfxPoolItem* pNew(nullptr);
 
        if (IsInvalidItem(pFnd2))
            // Decision table: default, dontcare, doesn't matter, doesn't matter
            pNew = INVALID_POOL_ITEM;
 
        else if (pFnd2 && !bIgnoreDefaults && GetPool()->GetUserOrPoolDefaultItem(nWhich) != *pFnd2)
            // Decision table: default, set, !=, sal_False
            pNew = INVALID_POOL_ITEM;
 
        else if (pFnd2 && bIgnoreDefaults)
            // Decision table: default, set, doesn't matter, sal_True
            pNew = implCreateItemEntry(*GetPool(), pFnd2, false);
 
        if (pNew)
        {
#ifdef DBG_UTIL
            assert(0 == m_nRegisteredSfxItemIter && "ITEM: SfxItemSet MergeItem with active SfxItemIters (!)");
#endif
            m_aPoolItemMap[nWhich] = pNew;
            checkAddPoolRegistration(pNew);
        }
 
        return;
    }
 
    const SfxPoolItem* pFnd1(aHit->second);
 
    if (IsInvalidItem(pFnd1))
    {
        return;
    }
 
    // 1st Item is set, check for change
    bool bDoChange(false);
 
    if (nullptr == pFnd2)
    {
        // 2nd Item is not set (Default)
        if (!bIgnoreDefaults && *pFnd1 != GetPool()->GetUserOrPoolDefaultItem(nWhich))
        {
            // Decision table: set, default, !=, sal_False
            bDoChange = true;
        }
    }
    else if (IsInvalidItem(pFnd2))
    {
        // 2nd Item is invalid (dontcare)
        if (!bIgnoreDefaults || *pFnd1 != GetPool()->GetUserOrPoolDefaultItem(nWhich))
        {
            // Decision table: set, dontcare, doesn't matter, sal_False
            // or:             set, dontcare, !=, sal_True
            bDoChange = true;
        }
    }
    else if (*pFnd1 != *pFnd2)
    {
        // 2nd Item is set
        // Decision table: set, set, !=, doesn't matter
        bDoChange = true;
    }
 
    if (bDoChange)
    {
        ClearSingleItem_PrepareRemove(pFnd1);
        aHit->second = INVALID_POOL_ITEM;
    }
}
 
void SfxItemSet::MergeValues( const SfxItemSet& rSet )
{
    // WARNING! When making changes/fixing bugs, always update the table above!!
    assert( GetPool() == rSet.GetPool() && "MergeValues with different Pools" );
 
    // CAUTION: Old version did *different* things when the WhichRanges
    // were the same (true) or different (false) (which is an error/
    // false optimization):
    // true:  MergeItem_Impl was directly fed with SfxItem*'s
    //        for entry @this & @rSet
    // false: Looped over rSet WhichID's, fetched defaults from pool,
    //        fed all that to SfxItemSet::MergeValue which then
    //        evtl. could not find that WhichID in local WhichRanges
    // Better to loop over local WhichRanges (these get changed) and look
    // for Item with same WhichID in rSet, this is done now.
    for (auto const & rRange : GetRanges())
    {
        for (sal_uInt16 nWhich(rRange.first); nWhich <= rRange.second; nWhich++)
        {
            PoolItemMap::const_iterator aHit(rSet.m_aPoolItemMap.find(nWhich));
            const SfxPoolItem* src(aHit == rSet.m_aPoolItemMap.end() ? nullptr : aHit->second);
            MergeItem_Impl(nWhich, src, false/*bIgnoreDefaults*/);
        }
    }
}
 
void SfxItemSet::MergeValue(const SfxPoolItem& rAttr)
{
    if (IsDisabledItem(&rAttr))
        // DisabledItem, nothing to do
        return;
 
    if (GetRanges().doesContainWhich(rAttr.Which()))
    {
        MergeItem_Impl(rAttr.Which(), &rAttr, /*bIgnoreDefaults*/true);
    }
}
 
void SfxItemSet::DisableOrInvalidateItem_ForWhichID(bool bDisable, sal_uInt16 nWhich)
{
    PoolItemMap::iterator aHit(m_aPoolItemMap.find(nWhich));
 
    if (aHit != m_aPoolItemMap.end())
    {
        if (bDisable && IsDisabledItem(aHit->second))
            // already disabled item, done
            return;
 
        if (!bDisable && IsInvalidItem(aHit->second))
            // already invalid item, done
            return;
 
        // cleanup entry
        ClearSingleItem_PrepareRemove(aHit->second);
        aHit->second = bDisable ? DISABLED_POOL_ITEM : INVALID_POOL_ITEM;
    }
    else if (GetRanges().doesContainWhich(nWhich))
    {
#ifdef DBG_UTIL
        assert(0 == m_nRegisteredSfxItemIter && "ITEM: SfxItemSet DisableOrInvalidateItem with active SfxItemIters (!)");
#endif
        // new entry
        m_aPoolItemMap[nWhich] = bDisable ? DISABLED_POOL_ITEM : INVALID_POOL_ITEM;
    }
}
bool SfxItemSet::operator==(const SfxItemSet &rCmp) const
{
    return Equals( rCmp, true);
}
 
bool SfxItemSet::Equals(const SfxItemSet &rCmp, bool bComparePool) const
{
    // check if same incarnation
    if (this == &rCmp)
        return true;
 
    // check parents (if requested, also bComparePool)
    if (bComparePool && GetParent() != rCmp.GetParent())
        return false;
 
    // check pools (if requested)
    if (bComparePool && GetPool() != rCmp.GetPool())
        return false;
 
    // check count of set items
    if (Count() != rCmp.Count())
        return false;
 
    // both have no items, done
    if (0 == Count())
        return true;
 
    for (PoolItemMap::const_iterator aCandidate(m_aPoolItemMap.begin()); aCandidate != m_aPoolItemMap.end(); aCandidate++)
    {
        const SfxPoolItem *pItem1(nullptr);
        const SfxPoolItem *pItem2(nullptr);
        const sal_uInt16 nWhich(aCandidate->first);
        const SfxItemState aStateA(GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, false, &pItem1));
        const SfxItemState aStateB(rCmp.GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, false, &pItem2));
 
        if (aStateA != aStateB)
            return false;
 
        // only compare items if SfxItemState::SET, else the item ptrs are not set
        if (SfxItemState::SET == aStateA && !SfxPoolItem::areSame(pItem1, pItem2))
            return false;
    }
 
    return true;
}
 
std::unique_ptr<SfxItemSet> SfxItemSet::Clone(bool bItems, SfxItemPool *pToPool ) const
{
    if (pToPool && pToPool != GetPool())
    {
        std::unique_ptr<SfxItemSet> pNewSet(new SfxItemSet(*pToPool, GetRanges()));
        if ( bItems )
        {
            SfxWhichIter aIter(*pNewSet);
            sal_uInt16 nWhich = aIter.FirstWhich();
            while ( nWhich )
            {
                const SfxPoolItem* pItem;
                if ( SfxItemState::SET == GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, false, &pItem ) )
                    pNewSet->Put( *pItem );
                nWhich = aIter.NextWhich();
            }
        }
        return pNewSet;
    }
    else
        return std::unique_ptr<SfxItemSet>(bItems
                ? new SfxItemSet(*this)
                : new SfxItemSet(*GetPool(), GetRanges()));
}
 
SfxItemSet SfxItemSet::CloneAsValue(bool bItems, SfxItemPool *pToPool ) const
{
    // if you are trying to clone, then the thing you are cloning is polymorphic, which means
    // it cannot be cloned as a value
    assert((typeid(*this) == typeid(SfxItemSet)) && "cannot call this on a subclass of SfxItemSet");
 
    if (pToPool && pToPool != GetPool())
    {
        SfxItemSet aNewSet(*pToPool, GetRanges());
        if ( bItems )
        {
            SfxWhichIter aIter(aNewSet);
            sal_uInt16 nWhich = aIter.FirstWhich();
            while ( nWhich )
            {
                const SfxPoolItem* pItem;
                if ( SfxItemState::SET == GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, false, &pItem ) )
                    aNewSet.Put( *pItem );
                nWhich = aIter.NextWhich();
            }
        }
        return aNewSet;
    }
    else
        return bItems
                ? *this
                : SfxItemSet(*GetPool(), GetRanges());
}
 
void SfxItemSet::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxItemSet"));
    SfxItemIter aIter(*this);
    for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
    {
        if (IsInvalidItem(pItem))
        {
            (void)xmlTextWriterStartElement(pWriter, BAD_CAST("invalid"));
            (void)xmlTextWriterEndElement(pWriter);
        }
        else
        {
            pItem->dumpAsXml(pWriter);
        }
    }
    (void)xmlTextWriterEndElement(pWriter);
}
 
 
// ----------------------------------------------- class SfxAllItemSet
 
SfxAllItemSet::SfxAllItemSet( SfxItemPool &rPool )
:   SfxItemSet(rPool)
{
}
 
SfxAllItemSet::SfxAllItemSet(const SfxItemSet &rCopy)
:   SfxItemSet(rCopy)
{
}
 
/**
 * Explicitly define this ctor to avoid auto-generation by the compiler.
 * The compiler does not take the ctor with the 'const SfxItemSet&'!
 */
SfxAllItemSet::SfxAllItemSet(const SfxAllItemSet &rCopy)
:   SfxItemSet(rCopy)
{
}
 
/**
 * Putting with automatic extension of the WhichId with the ID of the Item.
 */
const SfxPoolItem* SfxAllItemSet::PutImpl( const SfxPoolItem& rItem, bool bPassingOwnership )
{
    MergeRange(rItem.Which(), rItem.Which());
    return SfxItemSet::PutImpl(rItem, bPassingOwnership);
}
 
std::unique_ptr<SfxItemSet> SfxAllItemSet::Clone(bool bItems, SfxItemPool *pToPool ) const
{
    if (pToPool && pToPool != GetPool())
    {
        std::unique_ptr<SfxAllItemSet> pNewSet(new SfxAllItemSet( *pToPool ));
        if ( bItems )
            pNewSet->Set( *this );
        return pNewSet;
    }
    else
        return std::unique_ptr<SfxItemSet>(bItems ? new SfxAllItemSet(*this) : new SfxAllItemSet(*GetPool()));
}
 
 
void WhichRangesContainer::CountRanges() const
{
    m_TotalCount = 0;
    for (const auto& rPair : *this)
        m_TotalCount += svl::detail::rangeSize(rPair.first, rPair.second);
}
 
WhichRangesContainer::WhichRangesContainer( const WhichPair* wids, sal_Int32 nSize )
: m_pairs(nullptr)
, m_size(nSize)
, m_TotalCount(0)
, m_aLastWhichPairOffset(INVALID_WHICHPAIR_OFFSET)
, m_aLastWhichPairFirst(0)
, m_aLastWhichPairSecond(0)
, m_bOwnRanges(true)
{
    auto p = new WhichPair[nSize];
    for (int i=0; i<nSize; ++i)
        p[i] = wids[i];
    m_pairs = p;
    CountRanges();
}
 
WhichRangesContainer::WhichRangesContainer(sal_uInt16 nWhichStart, sal_uInt16 nWhichEnd)
: m_pairs(nullptr)
, m_size(1)
, m_TotalCount(0)
, m_aLastWhichPairOffset(INVALID_WHICHPAIR_OFFSET)
, m_aLastWhichPairFirst(0)
, m_aLastWhichPairSecond(0)
, m_bOwnRanges(true)
{
    auto p = new WhichPair[1];
    p[0] = { nWhichStart, nWhichEnd };
    m_pairs = p;
    CountRanges();
}
 
WhichRangesContainer::WhichRangesContainer(WhichRangesContainer && other)
{
    std::swap(m_pairs, other.m_pairs);
    std::swap(m_size, other.m_size);
    std::swap(m_TotalCount, other.m_TotalCount);
    std::swap(m_aLastWhichPairOffset, other.m_aLastWhichPairOffset);
    std::swap(m_aLastWhichPairFirst, other.m_aLastWhichPairFirst);
    std::swap(m_aLastWhichPairSecond, other.m_aLastWhichPairSecond);
    std::swap(m_bOwnRanges, other.m_bOwnRanges);
}
 
WhichRangesContainer& WhichRangesContainer::operator=(WhichRangesContainer && other)
{
    std::swap(m_pairs, other.m_pairs);
    std::swap(m_size, other.m_size);
    std::swap(m_TotalCount, other.m_TotalCount);
    std::swap(m_aLastWhichPairOffset, other.m_aLastWhichPairOffset);
    std::swap(m_aLastWhichPairFirst, other.m_aLastWhichPairFirst);
    std::swap(m_aLastWhichPairSecond, other.m_aLastWhichPairSecond);
    std::swap(m_bOwnRanges, other.m_bOwnRanges);
    return *this;
}
 
WhichRangesContainer& WhichRangesContainer::operator=(WhichRangesContainer const & other)
{
    reset();
 
    m_size = other.m_size;
    m_TotalCount = other.m_TotalCount;
    m_aLastWhichPairOffset = other.m_aLastWhichPairOffset;
    m_aLastWhichPairFirst = other.m_aLastWhichPairFirst;
    m_aLastWhichPairSecond = other.m_aLastWhichPairSecond;
    m_bOwnRanges = other.m_bOwnRanges;
 
    if (m_bOwnRanges)
    {
        auto p = new WhichPair[m_size];
        for (int i=0; i<m_size; ++i)
            p[i] = other.m_pairs[i];
        m_pairs = p;
    }
    else
        m_pairs = other.m_pairs;
 
    return *this;
}
 
WhichRangesContainer::~WhichRangesContainer()
{
    reset();
}
 
bool WhichRangesContainer::operator==(WhichRangesContainer const & other) const
{
    if (m_size != other.m_size)
        return false;
    if (m_TotalCount != other.m_TotalCount)
        return false;
    if (m_pairs == other.m_pairs)
        return true;
    return std::equal(m_pairs, m_pairs + m_size, other.m_pairs, other.m_pairs + m_size);
}
 
 
void WhichRangesContainer::reset()
{
    if (m_bOwnRanges)
    {
        delete [] m_pairs;
        m_bOwnRanges = false;
    }
    m_pairs = nullptr;
    m_size = 0;
    m_TotalCount = 0;
    m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
    m_aLastWhichPairFirst = 0;
    m_aLastWhichPairSecond = 0;
}
 
#ifdef DBG_UTIL
static size_t g_nHit(0);
static size_t g_nMiss(1);
static bool g_bShowWhichRangesHitRate(getenv("SVL_SHOW_WHICHRANGES_HITRATE"));
static void isHit() { g_nHit++; }
static void isMiss()
{
    g_nMiss++;
    const double fHitRate(double(g_nHit) /double(g_nMiss));
    if (0 == g_nMiss % 1000 && g_bShowWhichRangesHitRate)
        SAL_WARN("svl", "ITEM: hits: " << g_nHit << " misses: " << g_nMiss << " hits/misses(rate): " << fHitRate);
}
#endif
 
bool WhichRangesContainer::doesContainWhich(sal_uInt16 nWhich) const
{
    // special case for single entry - happens often e.g. UI stuff
    if (m_size == 1)
    {
        if( m_pairs->first <= nWhich && nWhich <= m_pairs->second )
            return true;
 
        // we have only one WhichPair entry and it's not contained -> failed
        return false;
    }
 
    if (m_size == 0)
        return false;
 
    // check if nWhich is inside last successfully used WhichPair
    if (INVALID_WHICHPAIR_OFFSET != m_aLastWhichPairOffset
        && m_aLastWhichPairFirst <= nWhich
        && nWhich <= m_aLastWhichPairSecond)
    {
#ifdef DBG_UTIL
        isHit();
#endif
        // we can re-use the last found WhichPair
        return true;
    }
 
#ifdef DBG_UTIL
    isMiss();
#endif
 
    // we have to find the correct WhichPair, iterate linear. This
    // also directly updates the buffered m_aLastWhichPair* values
    m_aLastWhichPairOffset = 0;
 
    for (const WhichPair& rPair : *this)
    {
        // Within this range?
        if( rPair.first <= nWhich && nWhich <= rPair.second )
        {
            // found, remember parameters for buffered hits
            m_aLastWhichPairFirst = rPair.first;
            m_aLastWhichPairSecond = rPair.second;
 
            // ...and return
            return true;
        }
 
        m_aLastWhichPairOffset += rPair.second - rPair.first + 1;
    }
 
    // *need* to reset: if 1st WhichPair only one entry it could be 1
    // what could wrongly trigger re-use above for next search
    m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
 
    return false;
}
 
// Adds a range to which ranges, keeping the ranges in valid state (sorted, non-overlapping)
WhichRangesContainer WhichRangesContainer::MergeRange(sal_uInt16 nFrom,
                                       sal_uInt16 nTo) const
{
    assert(svl::detail::validRange(nFrom, nTo));
 
    if (empty())
        return WhichRangesContainer(nFrom, nTo);
 
    // reset buffer
    m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
 
    // create vector of ranges (sal_uInt16 pairs of lower and upper bound)
    const size_t nOldCount = size();
    // Allocate one item more than we already have.
    // In the worst case scenario we waste a little bit
    // of memory, but we avoid another allocation, which is more important.
    std::unique_ptr<WhichPair[]> aRangesTable(new WhichPair[nOldCount+1]);
    int aRangesTableSize = 0;
    bool bAdded = false;
    for (const auto& rPair : *this)
    {
        if (!bAdded && rPair.first >= nFrom)
        { // insert new range, keep ranges sorted
            aRangesTable[aRangesTableSize++] = { nFrom, nTo };
            bAdded = true;
        }
        // insert current range
        aRangesTable[aRangesTableSize++] = rPair;
    }
    if (!bAdded)
        aRangesTable[aRangesTableSize++] = { nFrom, nTo };
 
    // true if ranges overlap or adjoin, false if ranges are separate
    auto needMerge = [](WhichPair lhs, WhichPair rhs) {
        return (lhs.first - 1) <= rhs.second && (rhs.first - 1) <= lhs.second;
    };
 
    auto it = aRangesTable.get();
    auto endIt = aRangesTable.get() + aRangesTableSize;
    // we have at least one range at this point
    for (;;)
    {
        auto itNext = std::next(it);
        if (itNext == endIt)
            break;
        // check if neighbouring ranges overlap or adjoin
        if (needMerge(*it, *itNext))
        {
            // lower bounds are sorted, implies: it->first = min(it[0].first, it[1].first)
            it->second = std::max(it->second, itNext->second);
            // remove next element
            std::move(std::next(itNext), endIt, itNext);
            --aRangesTableSize;
            endIt = aRangesTable.get() + aRangesTableSize;
        }
        else
            ++it;
    }
 
    return WhichRangesContainer(std::move(aRangesTable), aRangesTableSize);
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V1053 Calling the 'Changed' virtual function indirectly in the destructor may lead to unexpected result at runtime. Check lines: 'itemset.cxx:316', 'itemset.cxx:441', 'itemset.cxx:359', 'itemset.hxx:110'.

V547 Expression '!"invalid Argument for eDontCareAs"' is always false.

V547 Expression '!"invalid Argument for eDefaultAs"' is always false.

V794 The assignment operator should be protected from the case of 'this == &other'.