/* -*- 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 <osl/diagnose.h>
#include <cppuhelper/implbase.hxx>
#include <cppuhelper/queryinterface.hxx>
#include <comphelper/propshlp.hxx>
#include <cppuhelper/exc_hlp.hxx>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/lang/DisposedException.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <memory>
#include <sal/log.hxx>

using namespace com::sun::star::uno;
using namespace com::sun::star::beans;
using namespace com::sun::star::lang;
using namespace cppu;

namespace comphelper
{
extern "C" {

static int compare_OUString_Property_Impl(const void* arg1, const void* arg2) noexcept
{
    return static_cast<OUString const*>(arg1)->compareTo(static_cast<Property const*>(arg2)->Name);
}
}

/**
 * The class which implements the PropertySetInfo interface.
 */

namespace
{
class OPropertySetHelperInfo_Impl : public WeakImplHelper<css::beans::XPropertySetInfo>
{
    Sequence<Property> aInfos;

public:
    explicit OPropertySetHelperInfo_Impl(IPropertyArrayHelper& rHelper_);

    // XPropertySetInfo-methods
    virtual Sequence<Property> SAL_CALL getProperties() override;
    virtual Property SAL_CALL getPropertyByName(const OUString& PropertyName) override;
    virtual sal_Bool SAL_CALL hasPropertyByName(const OUString& PropertyName) override;
};
}

/**
 * Create an object that implements XPropertySetInfo IPropertyArrayHelper.
 */
OPropertySetHelperInfo_Impl::OPropertySetHelperInfo_Impl(IPropertyArrayHelper& rHelper_)
    : aInfos(rHelper_.getProperties())
{
}

/**
 * Return the sequence of properties, which are provided through the constructor.
 */
Sequence<Property> OPropertySetHelperInfo_Impl::getProperties() { return aInfos; }

/**
 * Return the sequence of properties, which are provided through the constructor.
 */
Property OPropertySetHelperInfo_Impl::getPropertyByName(const OUString& PropertyName)
{
    Property* pR
        = static_cast<Property*>(bsearch(&PropertyName, aInfos.getConstArray(), aInfos.getLength(),
                                         sizeof(Property), compare_OUString_Property_Impl));
    if (!pR)
        throw UnknownPropertyException(PropertyName);

    return *pR;
}

/**
 * Return the sequence of properties, which are provided through the constructor.
 */
sal_Bool OPropertySetHelperInfo_Impl::hasPropertyByName(const OUString& PropertyName)
{
    Property* pR
        = static_cast<Property*>(bsearch(&PropertyName, aInfos.getConstArray(), aInfos.getLength(),
                                         sizeof(Property), compare_OUString_Property_Impl));
    return pR != nullptr;
}

OPropertySetHelper::OPropertySetHelper() {}

OPropertySetHelper::OPropertySetHelper(bool bIgnoreRuntimeExceptionsWhileFiring)
    : m_bIgnoreRuntimeExceptionsWhileFiring(bIgnoreRuntimeExceptionsWhileFiring)
{
}

/**
 * You must call disposing before.
 */
OPropertySetHelper::~OPropertySetHelper() {}

// XInterface
Any OPropertySetHelper::queryInterface(const css::uno::Type& rType)
{
    return ::cppu::queryInterface(rType, static_cast<XPropertySet*>(this),
                                  static_cast<XMultiPropertySet*>(this),
                                  static_cast<XFastPropertySet*>(this));
}

/**
 * called from the derivee's XTypeProvider::getTypes implementation
 */
css::uno::Sequence<css::uno::Type> OPropertySetHelper::getTypes()
{
    return { UnoType<css::beans::XPropertySet>::get(),
             UnoType<css::beans::XMultiPropertySet>::get(),
             UnoType<css::beans::XFastPropertySet>::get() };
}

// ComponentHelper
void OPropertySetHelper::disposing(std::unique_lock<std::mutex>& rGuard)
{
    // Create an event with this as sender
    Reference<XPropertySet> rSource = this;
    EventObject aEvt;
    aEvt.Source = rSource;

    // inform all listeners to release this object
    // The listener containers are automatically cleared
    aBoundLC.disposeAndClear(rGuard, aEvt);
    aVetoableLC.disposeAndClear(rGuard, aEvt);
}

Reference<XPropertySetInfo>
OPropertySetHelper::createPropertySetInfo(IPropertyArrayHelper& rProperties)
{
    return new OPropertySetHelperInfo_Impl(rProperties);
}

// XPropertySet
void OPropertySetHelper::setPropertyValue(const OUString& rPropertyName, const Any& rValue)
{
    // get the map table
    IPropertyArrayHelper& rPH = getInfoHelper();
    // map the name to the handle
    sal_Int32 nHandle = rPH.getHandleByName(rPropertyName);
    std::unique_lock aGuard(m_aMutex);
    setFastPropertyValueImpl(aGuard, nHandle, rValue);
}

// XPropertySet
Any OPropertySetHelper::getPropertyValue(const OUString& rPropertyName)
{
    std::unique_lock aGuard(m_aMutex);
    return getPropertyValueImpl(aGuard, rPropertyName);
}

Any OPropertySetHelper::getPropertyValueImpl(std::unique_lock<std::mutex>& rGuard,
                                             const OUString& rPropertyName)
{
    // get the map table
    IPropertyArrayHelper& rPH = getInfoHelper();
    // map the name to the handle
    sal_Int32 nHandle = rPH.getHandleByName(rPropertyName);
    if (nHandle == -1)
        throw UnknownPropertyException(rPropertyName);
    // call the method of the XFastPropertySet interface
    Any aAny;
    getFastPropertyValue(rGuard, aAny, nHandle);
    return aAny;
}

// XPropertySet
void OPropertySetHelper::addPropertyChangeListener(
    const OUString& rPropertyName, const Reference<XPropertyChangeListener>& rxListener)
{
    std::unique_lock aGuard(m_aMutex);
    OSL_ENSURE(!m_bDisposed, "object is disposed");
    if (m_bDisposed)
        return;

    // only add listeners if you are not disposed
    // a listener with no name means all properties
    if (!rPropertyName.isEmpty())
    {
        // get the map table
        IPropertyArrayHelper& rPH = getInfoHelper();
        // map the name to the handle
        sal_Int32 nHandle = rPH.getHandleByName(rPropertyName);
        if (nHandle == -1)
        {
            // property not known throw exception
            throw UnknownPropertyException(rPropertyName);
        }

        sal_Int16 nAttributes;
        rPH.fillPropertyMembersByHandle(nullptr, &nAttributes, nHandle);
        if (!(nAttributes & css::beans::PropertyAttribute::BOUND))
        {
            OSL_FAIL("add listener to an unbound property");
            // silent ignore this
            return;
        }
        // add the change listener to the helper container
        aBoundLC.addInterface(aGuard, nHandle, rxListener);
    }
    else
        // add the change listener to the helper container
        maPropertyChangeListeners.addInterface(aGuard, rxListener);
}

// XPropertySet
void OPropertySetHelper::removePropertyChangeListener(
    const OUString& rPropertyName, const Reference<XPropertyChangeListener>& rxListener)
{
    std::unique_lock aGuard(m_aMutex);
    OSL_ENSURE(!m_bDisposed, "object is disposed");
    // all listeners are automatically released in a dispose call
    if (m_bDisposed)
        return;

    if (!rPropertyName.isEmpty())
    {
        // get the map table
        IPropertyArrayHelper& rPH = getInfoHelper();
        // map the name to the handle
        sal_Int32 nHandle = rPH.getHandleByName(rPropertyName);
        if (nHandle == -1)
            // property not known throw exception
            throw UnknownPropertyException(rPropertyName);
        aBoundLC.removeInterface(aGuard, nHandle, rxListener);
    }
    else
    {
        // remove the change listener to the helper container
        maPropertyChangeListeners.removeInterface(aGuard, rxListener);
    }
}

// XPropertySet
void OPropertySetHelper::addVetoableChangeListener(
    const OUString& rPropertyName, const Reference<XVetoableChangeListener>& rxListener)
{
    std::unique_lock aGuard(m_aMutex);
    OSL_ENSURE(!m_bDisposed, "object is disposed");
    if (m_bDisposed)
        return;

    // only add listeners if you are not disposed
    // a listener with no name means all properties
    if (!rPropertyName.isEmpty())
    {
        // get the map table
        IPropertyArrayHelper& rPH = getInfoHelper();
        // map the name to the handle
        sal_Int32 nHandle = rPH.getHandleByName(rPropertyName);
        if (nHandle == -1)
        {
            // property not known throw exception
            throw UnknownPropertyException(rPropertyName);
        }

        sal_Int16 nAttributes;
        rPH.fillPropertyMembersByHandle(nullptr, &nAttributes, nHandle);
        if (!(nAttributes & PropertyAttribute::CONSTRAINED))
        {
            OSL_FAIL("addVetoableChangeListener, and property is not constrained");
            // silent ignore this
            return;
        }
        // add the vetoable listener to the helper container
        aVetoableLC.addInterface(aGuard, nHandle, rxListener);
    }
    else
        // add the vetoable listener to the helper container
        maVetoableChangeListeners.addInterface(aGuard, rxListener);
}

// XPropertySet
void OPropertySetHelper::removeVetoableChangeListener(
    const OUString& rPropertyName, const Reference<XVetoableChangeListener>& rxListener)
{
    std::unique_lock aGuard(m_aMutex);
    OSL_ENSURE(!m_bDisposed, "object is disposed");
    // all listeners are automatically released in a dispose call
    if (m_bDisposed)
        return;

    if (!rPropertyName.isEmpty())
    {
        // get the map table
        IPropertyArrayHelper& rPH = getInfoHelper();
        // map the name to the handle
        sal_Int32 nHandle = rPH.getHandleByName(rPropertyName);
        if (nHandle == -1)
        {
            // property not known throw exception
            throw UnknownPropertyException(rPropertyName);
        }
        // remove the vetoable listener to the helper container
        aVetoableLC.removeInterface(aGuard, nHandle, rxListener);
    }
    else
        // add the vetoable listener to the helper container
        maVetoableChangeListeners.removeInterface(aGuard, rxListener);
}

void OPropertySetHelper::setDependentFastPropertyValue(std::unique_lock<std::mutex>& rGuard,
                                                       sal_Int32 i_handle,
                                                       const css::uno::Any& i_value)
{
    sal_Int16 nAttributes(0);
    IPropertyArrayHelper& rInfo = getInfoHelper();
    if (!rInfo.fillPropertyMembersByHandle(nullptr, &nAttributes, i_handle))
        // unknown property
        throw UnknownPropertyException(OUString::number(i_handle));

    // no need to check for READONLY-ness of the property. The method is intended to be called internally, which
    // implies it might be invoked for properties which are read-only to the instance's clients, but well allowed
    // to change their value.

    Any aConverted, aOld;
    bool bChanged = convertFastPropertyValue(rGuard, aConverted, aOld, i_handle, i_value);
    if (!bChanged)
        return;

    // don't fire vetoable events. This method is called with our mutex locked, so calling into listeners would not be
    // a good idea. The caller is responsible for not invoking this for constrained properties.
    OSL_ENSURE((nAttributes & PropertyAttribute::CONSTRAINED) == 0,
               "OPropertySetHelper::setDependentFastPropertyValue: not to be used for constrained "
               "properties!");

    // actually set the new value
    try
    {
        setFastPropertyValue_NoBroadcast(rGuard, i_handle, aConverted);
    }
    catch (const UnknownPropertyException&)
    {
        throw; /* allowed to leave */
    }
    catch (const PropertyVetoException&)
    {
        throw; /* allowed to leave */
    }
    catch (const IllegalArgumentException&)
    {
        throw; /* allowed to leave */
    }
    catch (const WrappedTargetException&)
    {
        throw; /* allowed to leave */
    }
    catch (const RuntimeException&)
    {
        throw; /* allowed to leave */
    }
    catch (const Exception&)
    {
        // not allowed to leave this method
        WrappedTargetException aWrapped;
        aWrapped.TargetException = ::cppu::getCaughtException();
        aWrapped.Context = static_cast<XPropertySet*>(this);
        throw aWrapped;
    }

    // remember the handle/values, for the events to be fired later
    m_handles.push_back(i_handle);
    m_newValues.push_back(
        aConverted); // TODO: setFastPropertyValue notifies the unconverted value here ...?
    m_oldValues.push_back(aOld);
}

// XFastPropertySet
void OPropertySetHelper::setFastPropertyValue(sal_Int32 nHandle, const Any& rValue)
{
    std::unique_lock aGuard(m_aMutex);
    setFastPropertyValueImpl(aGuard, nHandle, rValue);
}

void OPropertySetHelper::setFastPropertyValueImpl(std::unique_lock<std::mutex>& rGuard,
                                                  sal_Int32 nHandle, const Any& rValue)
{
    OSL_ENSURE(!m_bDisposed, "object is disposed");

    IPropertyArrayHelper& rInfo = getInfoHelper();
    sal_Int16 nAttributes;
    if (!rInfo.fillPropertyMembersByHandle(nullptr, &nAttributes, nHandle))
    {
        // unknown property
        throw UnknownPropertyException(OUString::number(nHandle));
    }
    if (nAttributes & PropertyAttribute::READONLY)
        throw PropertyVetoException();

    Any aConvertedVal;
    Any aOldVal;

    // Will the property change?
    bool bChanged = convertFastPropertyValue(rGuard, aConvertedVal, aOldVal, nHandle, rValue);
    if (!bChanged)
        return;

    // Is it a constrained property?
    if (nAttributes & PropertyAttribute::CONSTRAINED)
    {
        // In aValue is the converted rValue
        // fire a constrained event
        // second parameter NULL means constrained
        fire(rGuard, &nHandle, &rValue, &aOldVal, 1, true);
    }

    try
    {
        // set the property to the new value
        setFastPropertyValue_NoBroadcast(rGuard, nHandle, aConvertedVal);
    }
    catch (const css::beans::UnknownPropertyException&)
    {
        throw; /* allowed to leave */
    }
    catch (const css::beans::PropertyVetoException&)
    {
        throw; /* allowed to leave */
    }
    catch (const css::lang::IllegalArgumentException&)
    {
        throw; /* allowed to leave */
    }
    catch (const css::lang::WrappedTargetException&)
    {
        throw; /* allowed to leave */
    }
    catch (const css::uno::RuntimeException&)
    {
        throw; /* allowed to leave */
    }
    catch (const css::uno::Exception& e)
    {
        // not allowed to leave this method
        css::lang::WrappedTargetException aWrap;
        aWrap.Context = static_cast<css::beans::XPropertySet*>(this);
        aWrap.TargetException <<= e;

        throw aWrap;
    }

    // file a change event, if the value changed
    impl_fireAll(rGuard, &nHandle, &rValue, &aOldVal, 1);
}

// XFastPropertySet
Any OPropertySetHelper::getFastPropertyValue(sal_Int32 nHandle)
{
    IPropertyArrayHelper& rInfo = getInfoHelper();
    if (!rInfo.fillPropertyMembersByHandle(nullptr, nullptr, nHandle))
        // unknown property
        throw UnknownPropertyException(OUString::number(nHandle));

    Any aRet;
    std::unique_lock aGuard(m_aMutex);
    getFastPropertyValue(aGuard, aRet, nHandle);
    return aRet;
}

void OPropertySetHelper::impl_fireAll(std::unique_lock<std::mutex>& rGuard, sal_Int32* i_handles,
                                      const Any* i_newValues, const Any* i_oldValues,
                                      sal_Int32 i_count)
{
    if (m_handles.empty())
    {
        fire(rGuard, i_handles, i_newValues, i_oldValues, i_count, false);
        return;
    }

    const size_t additionalEvents = m_handles.size();
    OSL_ENSURE(additionalEvents == m_newValues.size() && additionalEvents == m_oldValues.size(),
               "OPropertySetHelper::impl_fireAll: inconsistency!");

    std::vector<sal_Int32> allHandles(additionalEvents + i_count);
    std::copy(m_handles.begin(), m_handles.end(), allHandles.begin());
    std::copy(i_handles, i_handles + i_count, allHandles.begin() + additionalEvents);

    std::vector<Any> allNewValues(additionalEvents + i_count);
    std::copy(m_newValues.begin(), m_newValues.end(), allNewValues.begin());
    std::copy(i_newValues, i_newValues + i_count, allNewValues.begin() + additionalEvents);

    std::vector<Any> allOldValues(additionalEvents + i_count);
    std::copy(m_oldValues.begin(), m_oldValues.end(), allOldValues.begin());
    std::copy(i_oldValues, i_oldValues + i_count, allOldValues.begin() + additionalEvents);

    m_handles.clear();
    m_newValues.clear();
    m_oldValues.clear();

    fire(rGuard, allHandles.data(), allNewValues.data(), allOldValues.data(),
         additionalEvents + i_count, false);
}

void OPropertySetHelper::fire(std::unique_lock<std::mutex>& rGuard, const sal_Int32* pnHandles,
                              const Any* pNewValues, const Any* pOldValues,
                              sal_Int32 nHandles, // This is the Count of the array
                              bool bVetoable)
{
    // Only fire, if one or more properties changed
    if (!nHandles)
        return;

    // create the event sequence of all changed properties
    Sequence<PropertyChangeEvent> aEvts(nHandles);
    PropertyChangeEvent* pEvts = aEvts.getArray();
    Reference<XInterface> xSource(static_cast<XPropertySet*>(this), UNO_QUERY);
    sal_Int32 i;
    sal_Int32 nChangesLen = 0;
    // Loop over all changed properties to fill the event struct
    for (i = 0; i < nHandles; i++)
    {
        // Vetoable fire and constrained attribute set or
        // Change fire and Changed and bound attribute set
        IPropertyArrayHelper& rInfo = getInfoHelper();
        sal_Int16 nAttributes;
        OUString aPropName;
        rInfo.fillPropertyMembersByHandle(&aPropName, &nAttributes, pnHandles[i]);

        if ((bVetoable && (nAttributes & PropertyAttribute::CONSTRAINED))
            || (!bVetoable && (nAttributes & PropertyAttribute::BOUND)))
        {
            pEvts[nChangesLen].Source = xSource;
            pEvts[nChangesLen].PropertyName = aPropName;
            pEvts[nChangesLen].PropertyHandle = pnHandles[i];
            pEvts[nChangesLen].OldValue = pOldValues[i];
            pEvts[nChangesLen].NewValue = pNewValues[i];
            nChangesLen++;
        }
    }

    bool bIgnoreRuntimeExceptionsWhileFiring = m_bIgnoreRuntimeExceptionsWhileFiring;

    // fire the events for all changed properties
    for (i = 0; i < nChangesLen; i++)
    {
        if (bVetoable) // fire change Events?
            fireVetoableChangeListeners(
                rGuard, aVetoableLC.getContainer(rGuard, pEvts[i].PropertyHandle), pEvts[i]);
        else
            // get the listener container for the property name
            firePropertyChangeListeners(
                rGuard, aBoundLC.getContainer(rGuard, pEvts[i].PropertyHandle), pEvts[i]);

        // broadcast to all listeners with "" property name
        if (bVetoable)
            // fire change Events?
            fireVetoableChangeListeners(rGuard, &maVetoableChangeListeners, pEvts[i]);
        else
            firePropertyChangeListeners(rGuard, &maPropertyChangeListeners, pEvts[i]);
    }

    // reduce array to changed properties
    aEvts.realloc(nChangesLen);

    if (bVetoable)
        return;

    if (!maPropertiesChangeListeners.getLength(rGuard))
        return;

    // Here is a Bug, unbound properties are also fired
    OInterfaceIteratorHelper4 aIt(rGuard, maPropertiesChangeListeners);
    rGuard.unlock();
    while (aIt.hasMoreElements())
    {
        XPropertiesChangeListener* pL = aIt.next().get();
        try
        {
            try
            {
                // fire the whole event sequence to the
                // XPropertiesChangeListener's
                pL->propertiesChange(aEvts);
            }
            catch (DisposedException& exc)
            {
                OSL_ENSURE(exc.Context.is(), "DisposedException without Context!");
                if (exc.Context == pL)
                {
                    rGuard.lock();
                    aIt.remove(rGuard);
                    rGuard.unlock();
                }
                else
                    throw;
            }
        }
        catch (RuntimeException& exc)
        {
            SAL_INFO("cppuhelper", "caught RuntimeException while firing listeners: " << exc);
            if (!bIgnoreRuntimeExceptionsWhileFiring)
                throw;
        }
    }
    rGuard.lock();
}

void OPropertySetHelper::fireVetoableChangeListeners(
    std::unique_lock<std::mutex>& rGuard,
    comphelper::OInterfaceContainerHelper4<css::beans::XVetoableChangeListener>* pListeners,
    const css::beans::PropertyChangeEvent& rChangeEvent)
{
    if (!pListeners || !pListeners->getLength(rGuard))
        return;
    // Iterate over all listeners and send events
    OInterfaceIteratorHelper4 aIt(rGuard, *pListeners);
    rGuard.unlock();
    while (aIt.hasMoreElements())
    {
        XVetoableChangeListener* pL = aIt.next().get();
        try
        {
            try
            {
                pL->vetoableChange(rChangeEvent);
            }
            catch (DisposedException& exc)
            {
                OSL_ENSURE(exc.Context.is(), "DisposedException without Context!");
                if (exc.Context == pL)
                {
                    rGuard.lock();
                    aIt.remove(rGuard);
                    rGuard.unlock();
                }
                else
                    throw;
            }
        }
        catch (RuntimeException& exc)
        {
            SAL_INFO("cppuhelper", "caught RuntimeException while firing listeners: " << exc);
            if (!m_bIgnoreRuntimeExceptionsWhileFiring)
                throw;
        }
    }
    rGuard.lock();
}

void OPropertySetHelper::firePropertyChangeListeners(
    std::unique_lock<std::mutex>& rGuard,
    comphelper::OInterfaceContainerHelper4<css::beans::XPropertyChangeListener>* pListeners,
    const css::beans::PropertyChangeEvent& rChangeEvent)
{
    if (!pListeners || !pListeners->getLength(rGuard))
        return;
    // Iterate over all listeners and send events
    OInterfaceIteratorHelper4 aIt(rGuard, *pListeners);
    rGuard.unlock();
    while (aIt.hasMoreElements())
    {
        XPropertyChangeListener* pL = aIt.next().get();
        try
        {
            try
            {
                pL->propertyChange(rChangeEvent);
            }
            catch (DisposedException& exc)
            {
                OSL_ENSURE(exc.Context.is(), "DisposedException without Context!");
                if (exc.Context == pL)
                {
                    rGuard.lock();
                    aIt.remove(rGuard);
                    rGuard.unlock();
                }
                else
                    throw;
            }
        }
        catch (RuntimeException& exc)
        {
            SAL_INFO("cppuhelper", "caught RuntimeException while firing listeners: " << exc);
            if (!m_bIgnoreRuntimeExceptionsWhileFiring)
                throw;
        }
    }
    rGuard.lock();
}

// OPropertySetHelper
void OPropertySetHelper::setFastPropertyValues(std::unique_lock<std::mutex>& rGuard,
                                               sal_Int32 nSeqLen, sal_Int32* pHandles,
                                               const Any* pValues, sal_Int32 nHitCount)
{
    OSL_ENSURE(!m_bDisposed, "object is disposed");

    // get the map table
    IPropertyArrayHelper& rPH = getInfoHelper();

    std::unique_ptr<Any[]> pConvertedValues(new Any[nHitCount]);
    std::unique_ptr<Any[]> pOldValues(new Any[nHitCount]);
    sal_Int32 n = 0;
    sal_Int32 i;

    for (i = 0; i < nSeqLen; i++)
    {
        if (pHandles[i] != -1)
        {
            sal_Int16 nAttributes;
            rPH.fillPropertyMembersByHandle(nullptr, &nAttributes, pHandles[i]);
            if (nAttributes & PropertyAttribute::READONLY)
                throw PropertyVetoException();
            // Will the property change?
            if (convertFastPropertyValue(rGuard, pConvertedValues[n], pOldValues[n], pHandles[i],
                                         pValues[i]))
            {
                // only increment if the property really change
                pHandles[n] = pHandles[i];
                n++;
            }
        }
    }

    // fire vetoable events
    fire(rGuard, pHandles, pConvertedValues.get(), pOldValues.get(), n, true);

    // Loop over all changed properties
    for (i = 0; i < n; i++)
    {
        // Will the property change?
        setFastPropertyValue_NoBroadcast(rGuard, pHandles[i], pConvertedValues[i]);
    }

    // fire change events
    impl_fireAll(rGuard, pHandles, pConvertedValues.get(), pOldValues.get(), n);
}

// XMultiPropertySet
/**
 * The sequence may be contain not known properties. The implementation
 * must ignore these properties.
 */
void OPropertySetHelper::setPropertyValues(const Sequence<OUString>& rPropertyNames,
                                           const Sequence<Any>& rValues)
{
    sal_Int32 nSeqLen = rPropertyNames.getLength();
    if (nSeqLen != rValues.getLength())
        throw IllegalArgumentException(u"lengths do not match"_ustr,
                                       static_cast<XPropertySet*>(this), -1);
    std::unique_ptr<sal_Int32[]> pHandles(new sal_Int32[nSeqLen]);
    // get the map table
    IPropertyArrayHelper& rPH = getInfoHelper();
    // fill the handle array
    sal_Int32 nHitCount = rPH.fillHandles(pHandles.get(), rPropertyNames);
    if (nHitCount == 0)
        return;
    std::unique_lock aGuard(m_aMutex);
    setFastPropertyValues(aGuard, nSeqLen, pHandles.get(), rValues.getConstArray(), nHitCount);
}

// XMultiPropertySet
Sequence<Any> OPropertySetHelper::getPropertyValues(const Sequence<OUString>& rPropertyNames)
{
    sal_Int32 nSeqLen = rPropertyNames.getLength();
    std::unique_ptr<sal_Int32[]> pHandles(new sal_Int32[nSeqLen]);
    Sequence<Any> aValues(nSeqLen);

    // get the map table
    IPropertyArrayHelper& rPH = getInfoHelper();
    // fill the handle array
    rPH.fillHandles(pHandles.get(), rPropertyNames);

    Any* pValues = aValues.getArray();

    std::unique_lock aGuard(m_aMutex);
    // fill the sequence with the values
    for (sal_Int32 i = 0; i < nSeqLen; i++)
        getFastPropertyValue(aGuard, pValues[i], pHandles[i]);

    return aValues;
}

// XMultiPropertySet
void OPropertySetHelper::addPropertiesChangeListener(
    const Sequence<OUString>&, const Reference<XPropertiesChangeListener>& rListener)
{
    std::unique_lock g(m_aMutex);
    maPropertiesChangeListeners.addInterface(g, rListener);
}

// XMultiPropertySet
void OPropertySetHelper::removePropertiesChangeListener(
    const Reference<XPropertiesChangeListener>& rListener)
{
    std::unique_lock g(m_aMutex);
    maPropertiesChangeListeners.removeInterface(g, rListener);
}

// XMultiPropertySet
void OPropertySetHelper::firePropertiesChangeEvent(
    const Sequence<OUString>& rPropertyNames, const Reference<XPropertiesChangeListener>& rListener)
{
    sal_Int32 nLen = rPropertyNames.getLength();
    std::unique_ptr<sal_Int32[]> pHandles(new sal_Int32[nLen]);
    IPropertyArrayHelper& rPH = getInfoHelper();
    rPH.fillHandles(pHandles.get(), rPropertyNames);

    // get the count of matching properties
    sal_Int32 nFireLen = 0;
    sal_Int32 i;
    for (i = 0; i < nLen; i++)
        if (pHandles[i] != -1)
            nFireLen++;

    Sequence<PropertyChangeEvent> aChanges(nFireLen);
    PropertyChangeEvent* pChanges = aChanges.getArray();

    {
        // must lock the mutex outside the loop. So all values are consistent.
        std::unique_lock aGuard(m_aMutex);
        Reference<XInterface> xSource(static_cast<XPropertySet*>(this), UNO_QUERY);
        sal_Int32 nFirePos = 0;
        for (i = 0; i < nLen; i++)
        {
            if (pHandles[i] != -1)
            {
                pChanges[nFirePos].Source = xSource;
                pChanges[nFirePos].PropertyName = rPropertyNames[i];
                pChanges[nFirePos].PropertyHandle = pHandles[i];
                getFastPropertyValue(aGuard, pChanges[nFirePos].OldValue, pHandles[i]);
                pChanges[nFirePos].NewValue = pChanges[nFirePos].OldValue;
                nFirePos++;
            }
        }
        // release guard to fire events
    }
    if (nFireLen)
        rListener->propertiesChange(aChanges);
}

UnoImplBase::~UnoImplBase() {}

} // end namespace comphelper

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
