diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /comphelper/source/container | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comphelper/source/container')
-rw-r--r-- | comphelper/source/container/IndexedPropertyValuesContainer.cxx | 127 | ||||
-rw-r--r-- | comphelper/source/container/NamedPropertyValuesContainer.cxx | 169 | ||||
-rw-r--r-- | comphelper/source/container/container.cxx | 140 | ||||
-rw-r--r-- | comphelper/source/container/containermultiplexer.cxx | 159 | ||||
-rw-r--r-- | comphelper/source/container/embeddedobjectcontainer.cxx | 1512 | ||||
-rw-r--r-- | comphelper/source/container/enumerablemap.cxx | 713 | ||||
-rw-r--r-- | comphelper/source/container/enumhelper.cxx | 280 | ||||
-rw-r--r-- | comphelper/source/container/interfacecontainer2.cxx | 424 | ||||
-rw-r--r-- | comphelper/source/container/namecontainer.cxx | 166 |
9 files changed, 3690 insertions, 0 deletions
diff --git a/comphelper/source/container/IndexedPropertyValuesContainer.cxx b/comphelper/source/container/IndexedPropertyValuesContainer.cxx new file mode 100644 index 0000000000..f5b7358d64 --- /dev/null +++ b/comphelper/source/container/IndexedPropertyValuesContainer.cxx @@ -0,0 +1,127 @@ +/* -*- 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 <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <comphelper/indexedpropertyvalues.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/safeint.hxx> + +namespace com::sun::star::uno { class XComponentContext; } + +using namespace com::sun::star; + + +namespace comphelper { + + +IndexedPropertyValuesContainer::IndexedPropertyValuesContainer() noexcept +{ +} + +// XIndexContainer +void SAL_CALL IndexedPropertyValuesContainer::insertByIndex( sal_Int32 nIndex, const css::uno::Any& aElement ) +{ + sal_Int32 nSize(maProperties.size()); + if ((nSize < nIndex) || (nIndex < 0)) + throw lang::IndexOutOfBoundsException(); + + uno::Sequence<beans::PropertyValue> aProps; + if (!(aElement >>= aProps)) + throw lang::IllegalArgumentException("element is not beans::PropertyValue", static_cast<cppu::OWeakObject*>(this), 2); + if (nSize == nIndex) + maProperties.push_back(aProps); + else + maProperties.insert(maProperties.begin() + nIndex, aProps); +} + +void SAL_CALL IndexedPropertyValuesContainer::removeByIndex( sal_Int32 nIndex ) +{ + if ((nIndex < 0) || (o3tl::make_unsigned(nIndex) >= maProperties.size())) + throw lang::IndexOutOfBoundsException(); + + maProperties.erase(maProperties.begin() + nIndex); +} + +// XIndexReplace +void SAL_CALL IndexedPropertyValuesContainer::replaceByIndex( sal_Int32 nIndex, const css::uno::Any& aElement ) +{ + sal_Int32 nSize(maProperties.size()); + if ((nIndex >= nSize) || (nIndex < 0)) + throw lang::IndexOutOfBoundsException(); + + uno::Sequence<beans::PropertyValue> aProps; + if (!(aElement >>= aProps)) + throw lang::IllegalArgumentException("element is not beans::PropertyValue", static_cast<cppu::OWeakObject*>(this), 2); + maProperties[nIndex] = aProps; +} + +// XIndexAccess +sal_Int32 SAL_CALL IndexedPropertyValuesContainer::getCount( ) +{ + return maProperties.size(); +} + +css::uno::Any SAL_CALL IndexedPropertyValuesContainer::getByIndex( sal_Int32 nIndex ) +{ + sal_Int32 nSize(maProperties.size()); + if ((nIndex >= nSize) || (nIndex < 0)) + throw lang::IndexOutOfBoundsException(); + + return uno::Any( maProperties[nIndex] ); +} + +// XElementAccess +css::uno::Type SAL_CALL IndexedPropertyValuesContainer::getElementType( ) +{ + return cppu::UnoType<uno::Sequence<beans::PropertyValue>>::get(); +} + +sal_Bool SAL_CALL IndexedPropertyValuesContainer::hasElements( ) +{ + return !maProperties.empty(); +} + +//XServiceInfo +OUString SAL_CALL IndexedPropertyValuesContainer::getImplementationName( ) +{ + return "IndexedPropertyValuesContainer"; +} + +sal_Bool SAL_CALL IndexedPropertyValuesContainer::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL IndexedPropertyValuesContainer::getSupportedServiceNames( ) +{ + return { "com.sun.star.document.IndexedPropertyValues" }; +} + +} // namespace comphelper + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +IndexedPropertyValuesContainer_get_implementation( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new comphelper::IndexedPropertyValuesContainer()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/container/NamedPropertyValuesContainer.cxx b/comphelper/source/container/NamedPropertyValuesContainer.cxx new file mode 100644 index 0000000000..a44837f117 --- /dev/null +++ b/comphelper/source/container/NamedPropertyValuesContainer.cxx @@ -0,0 +1,169 @@ +/* -*- 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 <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <comphelper/sequence.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <map> + + +namespace com::sun::star::uno { class XComponentContext; } +using namespace com::sun::star; + +typedef std::map< OUString, uno::Sequence<beans::PropertyValue> > NamedPropertyValues; + +namespace { + +class NamedPropertyValuesContainer : public cppu::WeakImplHelper< container::XNameContainer, lang::XServiceInfo > +{ +public: + NamedPropertyValuesContainer() noexcept; + + // XNameContainer + virtual void SAL_CALL insertByName( const OUString& aName, const css::uno::Any& aElement ) override; + virtual void SAL_CALL removeByName( const OUString& Name ) override; + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& aName, const css::uno::Any& aElement ) override; + + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType( ) override; + virtual sal_Bool SAL_CALL hasElements( ) override; + + //XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + +private: + NamedPropertyValues maProperties; +}; + +} + +NamedPropertyValuesContainer::NamedPropertyValuesContainer() noexcept +{ +} + +// XNameContainer +void SAL_CALL NamedPropertyValuesContainer::insertByName( const OUString& aName, const uno::Any& aElement ) +{ + if( maProperties.find( aName ) != maProperties.end() ) + throw container::ElementExistException(); + + uno::Sequence<beans::PropertyValue> aProps; + if( !(aElement >>= aProps ) ) + throw lang::IllegalArgumentException("element is not beans::PropertyValue", static_cast<cppu::OWeakObject*>(this), 2); + + maProperties.emplace( aName, aProps ); +} + +void SAL_CALL NamedPropertyValuesContainer::removeByName( const OUString& Name ) +{ + NamedPropertyValues::iterator aIter = maProperties.find( Name ); + if( aIter == maProperties.end() ) + throw container::NoSuchElementException(); + + maProperties.erase( aIter ); +} + +// XNameReplace +void SAL_CALL NamedPropertyValuesContainer::replaceByName( const OUString& aName, const css::uno::Any& aElement ) +{ + NamedPropertyValues::iterator aIter = maProperties.find( aName ); + if( aIter == maProperties.end() ) + throw container::NoSuchElementException(); + + uno::Sequence<beans::PropertyValue> aProps; + if( !(aElement >>= aProps) ) + throw lang::IllegalArgumentException("element is not beans::PropertyValue", static_cast<cppu::OWeakObject*>(this), 2); + + (*aIter).second = aProps; +} + +// XNameAccess +css::uno::Any SAL_CALL NamedPropertyValuesContainer::getByName( const OUString& aName ) +{ + NamedPropertyValues::iterator aIter = maProperties.find( aName ); + if( aIter == maProperties.end() ) + throw container::NoSuchElementException(); + + uno::Any aElement; + + aElement <<= (*aIter).second; + + return aElement; +} + +css::uno::Sequence< OUString > SAL_CALL NamedPropertyValuesContainer::getElementNames( ) +{ + return comphelper::mapKeysToSequence(maProperties); +} + +sal_Bool SAL_CALL NamedPropertyValuesContainer::hasByName( const OUString& aName ) +{ + NamedPropertyValues::iterator aIter = maProperties.find( aName ); + return aIter != maProperties.end(); +} + +// XElementAccess +css::uno::Type SAL_CALL NamedPropertyValuesContainer::getElementType( ) +{ + return cppu::UnoType<uno::Sequence<beans::PropertyValue>>::get(); +} + +sal_Bool SAL_CALL NamedPropertyValuesContainer::hasElements( ) +{ + return !maProperties.empty(); +} + +//XServiceInfo +OUString SAL_CALL NamedPropertyValuesContainer::getImplementationName( ) +{ + return "NamedPropertyValuesContainer"; +} + +sal_Bool SAL_CALL NamedPropertyValuesContainer::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL NamedPropertyValuesContainer::getSupportedServiceNames( ) +{ + return { "com.sun.star.document.NamedPropertyValues" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +NamedPropertyValuesContainer_get_implementation( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new NamedPropertyValuesContainer()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/container/container.cxx b/comphelper/source/container/container.cxx new file mode 100644 index 0000000000..7b24327233 --- /dev/null +++ b/comphelper/source/container/container.cxx @@ -0,0 +1,140 @@ +/* -*- 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 <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <comphelper/container.hxx> +#include <o3tl/any.hxx> +#include <utility> +#include <osl/diagnose.h> + + +namespace comphelper +{ + + +IndexAccessIterator::IndexAccessIterator(css::uno::Reference< css::uno::XInterface> xStartingPoint) + :m_xStartingPoint(std::move(xStartingPoint)) +{ + OSL_ENSURE(m_xStartingPoint.is(), "IndexAccessIterator::IndexAccessIterator : no starting point !"); +} + +IndexAccessIterator::~IndexAccessIterator() {} + + +css::uno::Reference< css::uno::XInterface> const & IndexAccessIterator::Next() +{ + bool bCheckingStartingPoint = !m_xCurrentObject.is(); + // Is the current node the starting point? + bool bAlreadyCheckedCurrent = m_xCurrentObject.is(); + // Have I already tested the current node through ShouldHandleElement? + if (!m_xCurrentObject.is()) + m_xCurrentObject = m_xStartingPoint; + + css::uno::Reference< css::uno::XInterface> xSearchLoop( m_xCurrentObject); + bool bHasMoreToSearch = true; + bool bFoundSomething = false; + while (!bFoundSomething && bHasMoreToSearch) + { + // Priming loop + if (!bAlreadyCheckedCurrent && ShouldHandleElement(xSearchLoop)) + { + m_xCurrentObject = xSearchLoop; + bFoundSomething = true; + } + else + { + // First, check to see if there's a match below + css::uno::Reference< css::container::XIndexAccess> xContainerAccess(xSearchLoop, css::uno::UNO_QUERY); + if (xContainerAccess.is() && xContainerAccess->getCount() && ShouldStepInto(xContainerAccess)) + { + css::uno::Any aElement(xContainerAccess->getByIndex(0)); + xSearchLoop = *o3tl::doAccess<css::uno::Reference<css::uno::XInterface>>(aElement); + bCheckingStartingPoint = false; + + m_arrChildIndizies.push_back(sal_Int32(0)); + } + else + { // otherwise, look above and to the right, if possible + while (!m_arrChildIndizies.empty()) + { // If the list isn't empty and there's nothing above + css::uno::Reference< css::container::XChild> xChild(xSearchLoop, css::uno::UNO_QUERY); + OSL_ENSURE(xChild.is(), "IndexAccessIterator::Next : a content has no appropriate interface !"); + + css::uno::Reference< css::uno::XInterface> xParent( xChild->getParent()); + xContainerAccess.set(xParent, css::uno::UNO_QUERY); + OSL_ENSURE(xContainerAccess.is(), "IndexAccessIterator::Next : a content has an invalid parent !"); + + // Remove the index that SearchLoop had within this parent from my stack + sal_Int32 nOldSearchChildIndex = m_arrChildIndizies[m_arrChildIndizies.size() - 1]; + m_arrChildIndizies.pop_back(); + + if (nOldSearchChildIndex < xContainerAccess->getCount() - 1) + { // Move to the right in this row + ++nOldSearchChildIndex; + // and check the next child + css::uno::Any aElement(xContainerAccess->getByIndex(nOldSearchChildIndex)); + xSearchLoop = *o3tl::doAccess<css::uno::Reference<css::uno::XInterface>>(aElement); + bCheckingStartingPoint = false; + // and update its position in the list. + m_arrChildIndizies.push_back(nOldSearchChildIndex); + + break; + } + // Finally, if there's nothing more to do in this row (to the right), we'll move on to the next row. + xSearchLoop = xParent; + bCheckingStartingPoint = false; + } + + if (m_arrChildIndizies.empty() && !bCheckingStartingPoint) + { //This is the case if there is nothing to the right in the original search loop + bHasMoreToSearch = false; + } + } + + if (bHasMoreToSearch) + { // If there is still a node in the tree which can be tested + if (ShouldHandleElement(xSearchLoop)) + { + m_xCurrentObject = xSearchLoop; + bFoundSomething = true; + } + else + if (bCheckingStartingPoint) + bHasMoreToSearch = false; + bAlreadyCheckedCurrent = true; + } + } + } + + if (!bFoundSomething) + { + OSL_ENSURE(m_arrChildIndizies.empty(), "IndexAccessIterator::Next : items left on stack ! how this ?"); + Invalidate(); + } + + return m_xCurrentObject; +} + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/container/containermultiplexer.cxx b/comphelper/source/container/containermultiplexer.cxx new file mode 100644 index 0000000000..c687e72793 --- /dev/null +++ b/comphelper/source/container/containermultiplexer.cxx @@ -0,0 +1,159 @@ +/* -*- 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 <comphelper/containermultiplexer.hxx> +#include <com/sun/star/container/XContainer.hpp> +#include <osl/diagnose.h> + +namespace comphelper +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::container; + + OContainerListener::OContainerListener(::osl::Mutex& _rMutex) + :m_rMutex(_rMutex) + { + } + + + OContainerListener::~OContainerListener() + { + if (m_xAdapter.is()) + { + m_xAdapter->dispose(); + } + } + + + void OContainerListener::_elementInserted( const ContainerEvent& /*_rEvent*/ ) + { + } + + + void OContainerListener::_elementRemoved( const ContainerEvent& ) + { + } + + + void OContainerListener::_elementReplaced( const ContainerEvent& /*_rEvent*/ ) + { + } + + + void OContainerListener::_disposing(const EventObject& ) + { + } + + + void OContainerListener::setAdapter(OContainerListenerAdapter* pAdapter) + { + ::osl::MutexGuard aGuard(m_rMutex); + m_xAdapter = pAdapter; + } + + OContainerListenerAdapter::OContainerListenerAdapter(OContainerListener* _pListener, + const Reference< XContainer >& _rxContainer) + :m_xContainer(_rxContainer) + ,m_pListener(_pListener) + { + if (m_pListener) + m_pListener->setAdapter(this); + + osl_atomic_increment(&m_refCount); + try + { + m_xContainer->addContainerListener(this); + } + catch(const Exception&) + { + OSL_FAIL("Exception caught!"); + } + osl_atomic_decrement(&m_refCount); + } + + + OContainerListenerAdapter::~OContainerListenerAdapter() + { + } + + + void OContainerListenerAdapter::dispose() + { + if (!m_xContainer.is()) + return; + + try + { + Reference< XContainerListener > xPreventDelete(this); + m_xContainer->removeContainerListener(xPreventDelete); + m_pListener->setAdapter(nullptr); + } + catch(const Exception&) + { + OSL_FAIL("Exception caught!"); + } + m_xContainer = nullptr; + m_pListener = nullptr; + } + + + void SAL_CALL OContainerListenerAdapter::disposing( const EventObject& _rSource) + { + if (m_pListener) + { + // tell the listener + m_pListener->_disposing(_rSource); + // disconnect the listener + if ( m_pListener ) + m_pListener->setAdapter(nullptr); + } + + m_xContainer = nullptr; + m_pListener = nullptr; + } + + + void SAL_CALL OContainerListenerAdapter::elementInserted( const ContainerEvent& _rEvent ) + { + if (m_pListener) + m_pListener->_elementInserted(_rEvent); + } + + + void SAL_CALL OContainerListenerAdapter::elementRemoved( const ContainerEvent& _rEvent ) + { + if (m_pListener) + m_pListener->_elementRemoved(_rEvent); + } + + + void SAL_CALL OContainerListenerAdapter::elementReplaced( const ContainerEvent& _rEvent ) + { + if (m_pListener) + m_pListener->_elementReplaced(_rEvent); + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/container/embeddedobjectcontainer.cxx b/comphelper/source/container/embeddedobjectcontainer.cxx new file mode 100644 index 0000000000..23915d3e13 --- /dev/null +++ b/comphelper/source/container/embeddedobjectcontainer.cxx @@ -0,0 +1,1512 @@ +/* -*- 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 <com/sun/star/container/XChild.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/embed/EmbeddedObjectCreator.hpp> +#include <com/sun/star/embed/WrongStateException.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/embed/XEmbedPersist.hpp> +#include <com/sun/star/embed/XLinkageSupport.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/embed/XOptimizedStorage.hpp> +#include <com/sun/star/embed/EntryInitModes.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/util/XModifiable.hpp> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/embed/EmbedMisc.hpp> + +#include <comphelper/seqstream.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/embeddedobjectcontainer.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <cppuhelper/weakref.hxx> +#include <sal/log.hxx> + +#include <officecfg/Office/Common.hxx> + +#include <algorithm> +#include <unordered_map> + + +using namespace ::com::sun::star; + +namespace comphelper { + +typedef std::unordered_map<OUString, uno::Reference<embed::XEmbeddedObject>> EmbeddedObjectContainerNameMap; +struct EmbedImpl +{ + // TODO/LATER: remove objects from temp. Container storage when object is disposed + EmbeddedObjectContainerNameMap maNameToObjectMap; + // to speed up lookup by Reference + std::unordered_map<uno::Reference<embed::XEmbeddedObject>, OUString> maObjectToNameMap; + uno::Reference < embed::XStorage > mxStorage; + EmbeddedObjectContainer* mpTempObjectContainer; + uno::Reference < embed::XStorage > mxImageStorage; + uno::WeakReference < uno::XInterface > m_xModel; + + bool mbOwnsStorage : 1; + bool mbUserAllowsLinkUpdate : 1; + + const uno::Reference < embed::XStorage >& GetReplacements(); +}; + +const uno::Reference < embed::XStorage >& EmbedImpl::GetReplacements() +{ + if ( !mxImageStorage.is() ) + { + try + { + mxImageStorage = mxStorage->openStorageElement( + "ObjectReplacements", embed::ElementModes::READWRITE ); + } + catch (const uno::Exception&) + { + mxImageStorage = mxStorage->openStorageElement( + "ObjectReplacements", embed::ElementModes::READ ); + } + } + + if ( !mxImageStorage.is() ) + throw io::IOException("No ObjectReplacements"); + + return mxImageStorage; +} + +EmbeddedObjectContainer::EmbeddedObjectContainer() + : pImpl(new EmbedImpl) +{ + pImpl->mxStorage = ::comphelper::OStorageHelper::GetTemporaryStorage(); + pImpl->mbOwnsStorage = true; + pImpl->mbUserAllowsLinkUpdate = true; + pImpl->mpTempObjectContainer = nullptr; +} + +EmbeddedObjectContainer::EmbeddedObjectContainer( const uno::Reference < embed::XStorage >& rStor ) + : pImpl(new EmbedImpl) +{ + pImpl->mxStorage = rStor; + pImpl->mbOwnsStorage = false; + pImpl->mbUserAllowsLinkUpdate = true; + pImpl->mpTempObjectContainer = nullptr; +} + +EmbeddedObjectContainer::EmbeddedObjectContainer( const uno::Reference < embed::XStorage >& rStor, const uno::Reference < uno::XInterface >& xModel ) + : pImpl(new EmbedImpl) +{ + pImpl->mxStorage = rStor; + pImpl->mbOwnsStorage = false; + pImpl->mbUserAllowsLinkUpdate = true; + pImpl->mpTempObjectContainer = nullptr; + pImpl->m_xModel = xModel; +} + +void EmbeddedObjectContainer::SwitchPersistence( const uno::Reference < embed::XStorage >& rStor ) +{ + ReleaseImageSubStorage(); + + if ( pImpl->mbOwnsStorage ) + pImpl->mxStorage->dispose(); + + pImpl->mxStorage = rStor; + pImpl->mbOwnsStorage = false; +} + +bool EmbeddedObjectContainer::CommitImageSubStorage() +{ + if ( !pImpl->mxImageStorage ) + return true; + + try + { + bool bReadOnlyMode = true; + uno::Reference < beans::XPropertySet > xSet(pImpl->mxImageStorage,uno::UNO_QUERY); + if ( xSet.is() ) + { + // get the open mode from the parent storage + sal_Int32 nMode = 0; + uno::Any aAny = xSet->getPropertyValue("OpenMode"); + if ( aAny >>= nMode ) + bReadOnlyMode = !(nMode & embed::ElementModes::WRITE ); + } // if ( xSet.is() ) + if ( !bReadOnlyMode ) + { + uno::Reference< embed::XTransactedObject > xTransact( pImpl->mxImageStorage, uno::UNO_QUERY_THROW ); + xTransact->commit(); + } + } + catch (const uno::Exception&) + { + return false; + } + + return true; +} + +void EmbeddedObjectContainer::ReleaseImageSubStorage() +{ + CommitImageSubStorage(); + + if ( pImpl->mxImageStorage.is() ) + { + try + { + pImpl->mxImageStorage->dispose(); + pImpl->mxImageStorage.clear(); + } + catch (const uno::Exception&) + { + SAL_WARN( "comphelper.container", "Problems releasing image substorage!" ); + } + } +} + +EmbeddedObjectContainer::~EmbeddedObjectContainer() +{ + ReleaseImageSubStorage(); + + if ( pImpl->mbOwnsStorage ) + pImpl->mxStorage->dispose(); + + delete pImpl->mpTempObjectContainer; +} + +void EmbeddedObjectContainer::CloseEmbeddedObjects() +{ + for( const auto& rObj : pImpl->maNameToObjectMap ) + { + uno::Reference < util::XCloseable > const & xClose = rObj.second; + if( xClose.is() ) + { + try + { + xClose->close( true ); + } + catch (const uno::Exception&) + { + } + } + } +} + +OUString EmbeddedObjectContainer::CreateUniqueObjectName() +{ + OUString aStr; + sal_Int32 i=1; + do + { + aStr = "Object " + OUString::number( i++ ); + } + while( HasEmbeddedObject( aStr ) ); + // TODO/LATER: should we consider deleted objects? + + return aStr; +} + +uno::Sequence < OUString > EmbeddedObjectContainer::GetObjectNames() const +{ + return comphelper::mapKeysToSequence(pImpl->maNameToObjectMap); +} + +bool EmbeddedObjectContainer::HasEmbeddedObjects() const +{ + return !pImpl->maNameToObjectMap.empty(); +} + +bool EmbeddedObjectContainer::HasEmbeddedObject( const OUString& rName ) +{ + auto aIt = pImpl->maNameToObjectMap.find( rName ); + if (aIt != pImpl->maNameToObjectMap.end()) + return true; + if (!pImpl->mxStorage.is()) + return false; + return pImpl->mxStorage->hasByName(rName); +} + +bool EmbeddedObjectContainer::HasEmbeddedObject( const uno::Reference < embed::XEmbeddedObject >& xObj ) const +{ + return pImpl->maObjectToNameMap.find(xObj) != pImpl->maObjectToNameMap.end(); +} + +bool EmbeddedObjectContainer::HasInstantiatedEmbeddedObject( const OUString& rName ) +{ + // allows to detect whether the object was already instantiated + // currently the filter instantiate it on loading, so this method allows + // to avoid objects pointing to the same persistence + auto aIt = pImpl->maNameToObjectMap.find( rName ); + return ( aIt != pImpl->maNameToObjectMap.end() ); +} + +OUString EmbeddedObjectContainer::GetEmbeddedObjectName( const css::uno::Reference < css::embed::XEmbeddedObject >& xObj ) const +{ + auto it = pImpl->maObjectToNameMap.find(xObj); + if (it == pImpl->maObjectToNameMap.end()) + { + SAL_WARN( "comphelper.container", "Unknown object!" ); + return OUString(); + } + return it->second; +} + +uno::Reference< embed::XEmbeddedObject> +EmbeddedObjectContainer::GetEmbeddedObject( + const OUString& rName, OUString const*const pBaseURL) +{ + SAL_WARN_IF( rName.isEmpty(), "comphelper.container", "Empty object name!"); + + uno::Reference < embed::XEmbeddedObject > xObj; + auto aIt = pImpl->maNameToObjectMap.find( rName ); + +#if OSL_DEBUG_LEVEL > 1 + uno::Reference < container::XNameAccess > xAccess( pImpl->mxStorage, uno::UNO_QUERY ); + uno::Sequence< OUString> aSeq = xAccess->getElementNames(); + const OUString* pIter = aSeq.getConstArray(); + const OUString* pEnd = pIter + aSeq.getLength(); + for(;pIter != pEnd;++pIter) + { + (void)*pIter; + } + OSL_ENSURE( aIt != pImpl->maNameToObjectMap.end() || xAccess->hasByName(rName), "Could not return object!" ); +#endif + + // check if object was already created + if ( aIt != pImpl->maNameToObjectMap.end() ) + xObj = (*aIt).second; + else + xObj = Get_Impl(rName, uno::Reference<embed::XEmbeddedObject>(), pBaseURL); + + return xObj; +} + +uno::Reference<embed::XEmbeddedObject> EmbeddedObjectContainer::Get_Impl( + const OUString& rName, + const uno::Reference<embed::XEmbeddedObject>& xCopy, + OUString const*const pBaseURL) +{ + uno::Reference < embed::XEmbeddedObject > xObj; + try + { + // create the object from the storage + uno::Reference < beans::XPropertySet > xSet( pImpl->mxStorage, uno::UNO_QUERY ); + bool bReadOnlyMode = true; + if ( xSet.is() ) + { + // get the open mode from the parent storage + sal_Int32 nMode = 0; + uno::Any aAny = xSet->getPropertyValue("OpenMode"); + if ( aAny >>= nMode ) + bReadOnlyMode = !(nMode & embed::ElementModes::WRITE ); + } + + // object was not added until now - should happen only by calling this method from "inside" + //TODO/LATER: it would be good to detect an error when an object should be created already, but isn't (not an "inside" call) + uno::Reference < embed::XEmbeddedObjectCreator > xFactory = embed::EmbeddedObjectCreator::create( ::comphelper::getProcessComponentContext() ); + uno::Sequence< beans::PropertyValue > aObjDescr(1 + (xCopy.is() ? 1 : 0) + (pBaseURL ? 1 : 0)); + auto itObjDescr = aObjDescr.getArray(); + itObjDescr->Name = "Parent"; + itObjDescr->Value <<= pImpl->m_xModel.get(); + if (pBaseURL) + { + ++itObjDescr; + itObjDescr->Name = "DefaultParentBaseURL"; + itObjDescr->Value <<= *pBaseURL; + } + if ( xCopy.is() ) + { + ++itObjDescr; + itObjDescr->Name = "CloneFrom"; + itObjDescr->Value <<= xCopy; + } + + uno::Sequence< beans::PropertyValue > aMediaDescr{ comphelper::makePropertyValue( + "ReadOnly", bReadOnlyMode) }; + xObj.set( xFactory->createInstanceInitFromEntry( + pImpl->mxStorage, rName, + aMediaDescr, aObjDescr ), uno::UNO_QUERY ); + + // insert object into my list + AddEmbeddedObject( xObj, rName ); + } + catch (uno::Exception const& e) + { + SAL_WARN("comphelper.container", "EmbeddedObjectContainer::Get_Impl: exception caught: " << e); + } + + return xObj; +} + +uno::Reference < embed::XEmbeddedObject > EmbeddedObjectContainer::CreateEmbeddedObject( const uno::Sequence < sal_Int8 >& rClassId, + const uno::Sequence < beans::PropertyValue >& rArgs, OUString& rNewName, OUString const* pBaseURL ) +{ + if ( rNewName.isEmpty() ) + rNewName = CreateUniqueObjectName(); + + SAL_WARN_IF( HasEmbeddedObject(rNewName), "comphelper.container", "Object to create already exists!"); + + // create object from classid by inserting it into storage + uno::Reference < embed::XEmbeddedObject > xObj; + try + { + uno::Reference < embed::XEmbeddedObjectCreator > xFactory = embed::EmbeddedObjectCreator::create( ::comphelper::getProcessComponentContext() ); + + const size_t nExtraArgs = pBaseURL ? 2 : 1; + uno::Sequence< beans::PropertyValue > aObjDescr( rArgs.getLength() + nExtraArgs ); + auto pObjDescr = aObjDescr.getArray(); + pObjDescr[0].Name = "Parent"; + pObjDescr[0].Value <<= pImpl->m_xModel.get(); + if (pBaseURL) + { + pObjDescr[1].Name = "DefaultParentBaseURL"; + pObjDescr[1].Value <<= *pBaseURL; + } + std::copy( rArgs.begin(), rArgs.end(), pObjDescr + nExtraArgs ); + xObj.set( xFactory->createInstanceInitNew( + rClassId, OUString(), pImpl->mxStorage, rNewName, + aObjDescr ), uno::UNO_QUERY ); + + AddEmbeddedObject( xObj, rNewName ); + + OSL_ENSURE( !xObj.is() || xObj->getCurrentState() != embed::EmbedStates::LOADED, + "A freshly create object should be running always!" ); + } + catch (uno::Exception const& e) + { + SAL_WARN("comphelper.container", "EmbeddedObjectContainer::CreateEmbeddedObject: exception caught: " << e); + } + + return xObj; +} + +uno::Reference < embed::XEmbeddedObject > EmbeddedObjectContainer::CreateEmbeddedObject( const uno::Sequence < sal_Int8 >& rClassId, OUString& rNewName, OUString const* pBaseURL ) +{ + return CreateEmbeddedObject( rClassId, uno::Sequence < beans::PropertyValue >(), rNewName, pBaseURL ); +} + +void EmbeddedObjectContainer::AddEmbeddedObject( const css::uno::Reference < css::embed::XEmbeddedObject >& xObj, const OUString& rName ) +{ +#if OSL_DEBUG_LEVEL > 1 + SAL_WARN_IF( rName.isEmpty(), "comphelper.container", "Added object doesn't have a name!"); + uno::Reference < container::XNameAccess > xAccess( pImpl->mxStorage, uno::UNO_QUERY ); + uno::Reference < embed::XEmbedPersist > xEmb( xObj, uno::UNO_QUERY ); + uno::Reference < embed::XLinkageSupport > xLink( xEmb, uno::UNO_QUERY ); + // if the object has a persistence and the object is not a link than it must have persistence entry in the storage + OSL_ENSURE( !( xEmb.is() && ( !xLink.is() || !xLink->isLink() ) ) || xAccess->hasByName(rName), + "Added element not in storage!" ); +#endif + + // remember object - it needs to be in storage already + auto aIt = pImpl->maNameToObjectMap.find( rName ); + OSL_ENSURE( aIt == pImpl->maNameToObjectMap.end(), "Element already inserted!" ); + pImpl->maNameToObjectMap[ rName ] = xObj; + pImpl->maObjectToNameMap[ xObj ] = rName; + uno::Reference < container::XChild > xChild( xObj, uno::UNO_QUERY ); + if ( xChild.is() && xChild->getParent() != pImpl->m_xModel.get() ) + xChild->setParent( pImpl->m_xModel.get() ); + + // look for object in temporary container + if ( !pImpl->mpTempObjectContainer ) + return; + + auto& rObjectContainer = pImpl->mpTempObjectContainer->pImpl->maNameToObjectMap; + auto aIter = std::find_if(rObjectContainer.begin(), rObjectContainer.end(), + [&xObj](const EmbeddedObjectContainerNameMap::value_type& rEntry) { return rEntry.second == xObj; }); + if (aIter == rObjectContainer.end()) + return; + + // copy replacement image from temporary container (if there is any) + OUString aTempName = aIter->first; + OUString aMediaType; + uno::Reference < io::XInputStream > xStream = pImpl->mpTempObjectContainer->GetGraphicStream( xObj, &aMediaType ); + if ( xStream.is() ) + { + InsertGraphicStream( xStream, rName, aMediaType ); + xStream = nullptr; + pImpl->mpTempObjectContainer->RemoveGraphicStream( aTempName ); + } + + // remove object from storage of temporary container + uno::Reference < embed::XEmbedPersist > xPersist( xObj, uno::UNO_QUERY ); + if ( xPersist.is() ) + { + try + { + pImpl->mpTempObjectContainer->pImpl->mxStorage->removeElement( aTempName ); + } + catch (const uno::Exception&) + { + } + } + + // temp. container needs to forget the object + pImpl->mpTempObjectContainer->pImpl->maObjectToNameMap.erase( aIter->second ); + pImpl->mpTempObjectContainer->pImpl->maNameToObjectMap.erase( aIter ); +} + +bool EmbeddedObjectContainer::StoreEmbeddedObject( + const uno::Reference < embed::XEmbeddedObject >& xObj, OUString& rName, bool bCopy, + const OUString& rSrcShellID, const OUString& rDestShellID ) +{ + uno::Reference < embed::XEmbedPersist > xPersist( xObj, uno::UNO_QUERY ); + if ( rName.isEmpty() ) + rName = CreateUniqueObjectName(); + +#if OSL_DEBUG_LEVEL > 1 + uno::Reference < container::XNameAccess > xAccess( pImpl->mxStorage, uno::UNO_QUERY ); + OSL_ENSURE( !xPersist.is() || !xAccess->hasByName(rName), "Inserting element already present in storage!" ); + OSL_ENSURE( xPersist.is() || xObj->getCurrentState() == embed::EmbedStates::RUNNING, "Non persistent object inserted!"); +#endif + + // insert objects' storage into the container storage (if object has one) + try + { + if ( xPersist.is() ) + { + uno::Sequence < beans::PropertyValue > aSeq; + if ( bCopy ) + { + auto aObjArgs(::comphelper::InitPropertySequence({ + { "SourceShellID", uno::Any(rSrcShellID) }, + { "DestinationShellID", uno::Any(rDestShellID) } + })); + xPersist->storeToEntry(pImpl->mxStorage, rName, aSeq, aObjArgs); + } + else + { + //TODO/LATER: possible optimization, don't store immediately + //xPersist->setPersistentEntry( pImpl->mxStorage, rName, embed::EntryInitModes::ENTRY_NO_INIT, aSeq, aSeq ); + xPersist->storeAsEntry( pImpl->mxStorage, rName, aSeq, aSeq ); + xPersist->saveCompleted( true ); + } + } + } + catch (uno::Exception const& e) + { + SAL_WARN("comphelper.container", "EmbeddedObjectContainer::StoreEmbeddedObject: exception caught: " << e); + // TODO/LATER: better error recovery should keep storage intact + return false; + } + + return true; +} + +bool EmbeddedObjectContainer::InsertEmbeddedObject( const uno::Reference < embed::XEmbeddedObject >& xObj, OUString& rName ) +{ + // store it into the container storage + if (StoreEmbeddedObject(xObj, rName, false, OUString(), OUString())) + { + // remember object + AddEmbeddedObject( xObj, rName ); + return true; + } + else + return false; +} + +uno::Reference < embed::XEmbeddedObject > EmbeddedObjectContainer::InsertEmbeddedObject( const uno::Reference < io::XInputStream >& xStm, OUString& rNewName ) +{ + if ( rNewName.isEmpty() ) + rNewName = CreateUniqueObjectName(); + + // store it into the container storage + bool bIsStorage = false; + try + { + // first try storage persistence + uno::Reference < embed::XStorage > xStore = ::comphelper::OStorageHelper::GetStorageFromInputStream( xStm ); + + // storage was created from stream successfully + bIsStorage = true; + + uno::Reference < embed::XStorage > xNewStore = pImpl->mxStorage->openStorageElement( rNewName, embed::ElementModes::READWRITE ); + xStore->copyToStorage( xNewStore ); + } + catch (const uno::Exception&) + { + if ( bIsStorage ) + // it is storage persistence, but opening of new substorage or copying to it failed + return uno::Reference < embed::XEmbeddedObject >(); + + // stream didn't contain a storage, now try stream persistence + try + { + uno::Reference < io::XStream > xNewStream = pImpl->mxStorage->openStreamElement( rNewName, embed::ElementModes::READWRITE ); + ::comphelper::OStorageHelper::CopyInputToOutput( xStm, xNewStream->getOutputStream() ); + + // No mediatype is provided so the default for OLE objects value is used + // it is correct so for now, but what if somebody introduces a new stream based embedded object? + // Probably introducing of such an object must be restricted ( a storage must be used! ). + uno::Reference< beans::XPropertySet > xProps( xNewStream, uno::UNO_QUERY_THROW ); + xProps->setPropertyValue("MediaType", + uno::Any( OUString( "application/vnd.sun.star.oleobject" ) ) ); + } + catch (uno::Exception const& e) + { + // complete disaster! + SAL_WARN("comphelper.container", "EmbeddedObjectContainer::InsertEmbeddedObject: exception caught: " << e); + return uno::Reference < embed::XEmbeddedObject >(); + } + } + + // stream was copied into the container storage in either way, now try to open something form it + uno::Reference < embed::XEmbeddedObject > xRet = GetEmbeddedObject( rNewName ); + try + { + if ( !xRet.is() ) + // no object could be created, so withdraw insertion + pImpl->mxStorage->removeElement( rNewName ); + } + catch (const uno::Exception&) + { + } + + return xRet; +} + +uno::Reference < embed::XEmbeddedObject > EmbeddedObjectContainer::InsertEmbeddedObject( const css::uno::Sequence < css::beans::PropertyValue >& aMedium, OUString& rNewName, OUString const* pBaseURL ) +{ + if ( rNewName.isEmpty() ) + rNewName = CreateUniqueObjectName(); + + uno::Reference < embed::XEmbeddedObject > xObj; + try + { + uno::Reference < embed::XEmbeddedObjectCreator > xFactory = embed::EmbeddedObjectCreator::create( ::comphelper::getProcessComponentContext() ); + uno::Sequence< beans::PropertyValue > aObjDescr(pBaseURL ? 2 : 1); + auto pObjDescr = aObjDescr.getArray(); + pObjDescr[0].Name = "Parent"; + pObjDescr[0].Value <<= pImpl->m_xModel.get(); + if (pBaseURL) + { + pObjDescr[1].Name = "DefaultParentBaseURL"; + pObjDescr[1].Value <<= *pBaseURL; + } + xObj.set( xFactory->createInstanceInitFromMediaDescriptor( + pImpl->mxStorage, rNewName, aMedium, aObjDescr ), uno::UNO_QUERY ); + uno::Reference < embed::XEmbedPersist > xPersist( xObj, uno::UNO_QUERY ); + + OSL_ENSURE( !xObj.is() || xObj->getCurrentState() != embed::EmbedStates::LOADED, + "A freshly create object should be running always!" ); + + // possible optimization: store later! + if ( xPersist.is()) + xPersist->storeOwn(); + + AddEmbeddedObject( xObj, rNewName ); + } + catch (const uno::Exception&) + { + } + + return xObj; +} + +uno::Reference < embed::XEmbeddedObject > EmbeddedObjectContainer::InsertEmbeddedLink( const css::uno::Sequence < css::beans::PropertyValue >& aMedium, OUString& rNewName ) +{ + if ( rNewName.isEmpty() ) + rNewName = CreateUniqueObjectName(); + + uno::Reference < embed::XEmbeddedObject > xObj; + try + { + uno::Reference < embed::XEmbeddedObjectCreator > xFactory = embed::EmbeddedObjectCreator::create(::comphelper::getProcessComponentContext()); + uno::Sequence< beans::PropertyValue > aObjDescr{ comphelper::makePropertyValue( + "Parent", pImpl->m_xModel.get()) }; + xObj.set( xFactory->createInstanceLink( pImpl->mxStorage, rNewName, aMedium, aObjDescr ), uno::UNO_QUERY ); + + uno::Reference < embed::XEmbedPersist > xPersist( xObj, uno::UNO_QUERY ); + + OSL_ENSURE( !xObj.is() || xObj->getCurrentState() != embed::EmbedStates::LOADED, + "A freshly create object should be running always!" ); + + // possible optimization: store later! + if ( xPersist.is()) + xPersist->storeOwn(); + + AddEmbeddedObject( xObj, rNewName ); + } + catch (uno::Exception const& e) + { + SAL_WARN("comphelper.container", "EmbeddedObjectContainer::InsertEmbeddedLink: " + "exception caught: " << e); + } + + return xObj; +} + +bool EmbeddedObjectContainer::TryToCopyGraphReplacement( EmbeddedObjectContainer& rSrc, + const OUString& aOrigName, + const OUString& aTargetName ) +{ + bool bResult = false; + + if ( ( &rSrc != this || aOrigName != aTargetName ) && !aOrigName.isEmpty() && !aTargetName.isEmpty() ) + { + OUString aMediaType; + uno::Reference < io::XInputStream > xGrStream = rSrc.GetGraphicStream( aOrigName, &aMediaType ); + if ( xGrStream.is() ) + bResult = InsertGraphicStream( xGrStream, aTargetName, aMediaType ); + } + + return bResult; +} + +uno::Reference < embed::XEmbeddedObject > EmbeddedObjectContainer::CopyAndGetEmbeddedObject( + EmbeddedObjectContainer& rSrc, const uno::Reference <embed::XEmbeddedObject>& xObj, OUString& rName, + const OUString& rSrcShellID, const OUString& rDestShellID ) +{ + uno::Reference< embed::XEmbeddedObject > xResult; + + // TODO/LATER: For now only objects that implement XEmbedPersist have a replacement image, it might change in future + // do an incompatible change so that object name is provided in all the move and copy methods + OUString aOrigName; + try + { + uno::Reference < embed::XEmbedPersist > xPersist( xObj, uno::UNO_QUERY_THROW ); + aOrigName = xPersist->getEntryName(); + } + catch (const uno::Exception&) + { + } + + if ( rName.isEmpty() ) + rName = CreateUniqueObjectName(); + + // objects without persistence are not really stored by the method + if (xObj.is() && StoreEmbeddedObject(xObj, rName, true, rSrcShellID, rDestShellID)) + { + SAL_INFO_IF(rDestShellID.isEmpty(), "comphelper.container", + "SfxObjectShell with no base URL?"); // every shell has a base URL, except the clipboard SwDocShell + xResult = Get_Impl(rName, xObj, &rDestShellID); + if ( !xResult.is() ) + { + // this is a case when object has no real persistence + // in such cases a new object should be explicitly created and initialized with the data of the old one + try + { + uno::Reference< embed::XLinkageSupport > xOrigLinkage( xObj, uno::UNO_QUERY ); + if ( xOrigLinkage.is() && xOrigLinkage->isLink() ) + { + // this is an OOo link, it has no persistence + OUString aURL = xOrigLinkage->getLinkURL(); + if ( aURL.isEmpty() ) + throw uno::RuntimeException(); + + // create new linked object from the URL the link is based on + uno::Reference < embed::XEmbeddedObjectCreator > xCreator = + embed::EmbeddedObjectCreator::create( ::comphelper::getProcessComponentContext() ); + + uno::Sequence< beans::PropertyValue > aMediaDescr{ comphelper::makePropertyValue( + "URL", aURL) }; + uno::Sequence< beans::PropertyValue > aObjDescr{ comphelper::makePropertyValue( + "Parent", pImpl->m_xModel.get()) }; + xResult.set(xCreator->createInstanceLink( + pImpl->mxStorage, + rName, + aMediaDescr, + aObjDescr ), + uno::UNO_QUERY_THROW ); + } + else + { + // the component is required for copying of this object + if ( xObj->getCurrentState() == embed::EmbedStates::LOADED ) + xObj->changeState( embed::EmbedStates::RUNNING ); + + // this must be an object based on properties, otherwise we can not copy it currently + uno::Reference< beans::XPropertySet > xOrigProps( xObj->getComponent(), uno::UNO_QUERY_THROW ); + + // use object class ID to create a new one and transfer all the properties + uno::Reference < embed::XEmbeddedObjectCreator > xCreator = + embed::EmbeddedObjectCreator::create( ::comphelper::getProcessComponentContext() ); + + uno::Sequence< beans::PropertyValue > aObjDescr{ comphelper::makePropertyValue( + "Parent", pImpl->m_xModel.get()) }; + xResult.set(xCreator->createInstanceInitNew( + xObj->getClassID(), + xObj->getClassName(), + pImpl->mxStorage, + rName, + aObjDescr ), + uno::UNO_QUERY_THROW ); + + if ( xResult->getCurrentState() == embed::EmbedStates::LOADED ) + xResult->changeState( embed::EmbedStates::RUNNING ); + + uno::Reference< beans::XPropertySet > xTargetProps( xResult->getComponent(), uno::UNO_QUERY_THROW ); + + // copy all the properties from xOrigProps to xTargetProps + uno::Reference< beans::XPropertySetInfo > xOrigInfo = xOrigProps->getPropertySetInfo(); + if ( !xOrigInfo.is() ) + throw uno::RuntimeException(); + + const uno::Sequence< beans::Property > aPropertiesList = xOrigInfo->getProperties(); + for ( const auto & p : aPropertiesList ) + { + try + { + xTargetProps->setPropertyValue( + p.Name, + xOrigProps->getPropertyValue( p.Name ) ); + } + catch (const beans::PropertyVetoException&) + { + // impossibility to copy readonly property is not treated as an error for now + // but the assertion is helpful to detect such scenarios and review them + SAL_WARN( "comphelper.container", "Could not copy readonly property!" ); + } + } + } + + if ( xResult.is() ) + AddEmbeddedObject( xResult, rName ); + } + catch (const uno::Exception&) + { + if ( xResult.is() ) + { + try + { + xResult->close( true ); + } + catch (const uno::Exception&) + { + } + xResult.clear(); + } + } + } + } + + SAL_WARN_IF( !xResult.is(), "comphelper.container", "Can not copy embedded object that has no persistence!" ); + + if ( xResult.is() ) + { + // the object is successfully copied, try to copy graphical replacement + if ( !aOrigName.isEmpty() ) + TryToCopyGraphReplacement( rSrc, aOrigName, rName ); + + // the object might need the size to be set + try + { + if ( xResult->getStatus( embed::Aspects::MSOLE_CONTENT ) & embed::EmbedMisc::EMBED_NEEDSSIZEONLOAD ) + xResult->setVisualAreaSize( embed::Aspects::MSOLE_CONTENT, + xObj->getVisualAreaSize( embed::Aspects::MSOLE_CONTENT ) ); + } + catch (const uno::Exception&) + { + } + } + + return xResult; +} + +// #i119941, bKeepToTempStorage: use to specify whether store the removed object to temporary storage+ +void EmbeddedObjectContainer::RemoveEmbeddedObject( const OUString& rName, bool bKeepToTempStorage ) +{ + uno::Reference < embed::XEmbeddedObject > xObj = GetEmbeddedObject( rName ); + if ( xObj.is() ) + RemoveEmbeddedObject( xObj, bKeepToTempStorage ); +} + +bool EmbeddedObjectContainer::MoveEmbeddedObject( const OUString& rName, EmbeddedObjectContainer& rCnt ) +{ + // find object entry + auto aIt2 = rCnt.pImpl->maNameToObjectMap.find( rName ); + OSL_ENSURE( aIt2 == rCnt.pImpl->maNameToObjectMap.end(), "Object does already exist in target container!" ); + + if ( aIt2 != rCnt.pImpl->maNameToObjectMap.end() ) + return false; + + uno::Reference < embed::XEmbeddedObject > xObj; + auto aIt = pImpl->maNameToObjectMap.find( rName ); + if ( aIt != pImpl->maNameToObjectMap.end() ) + { + xObj = (*aIt).second; + try + { + if ( xObj.is() ) + { + // move object + OUString aName( rName ); + rCnt.InsertEmbeddedObject( xObj, aName ); + pImpl->maObjectToNameMap.erase( aIt->second ); + pImpl->maNameToObjectMap.erase( aIt ); + uno::Reference < embed::XEmbedPersist > xPersist( xObj, uno::UNO_QUERY ); + if ( xPersist.is() ) + pImpl->mxStorage->removeElement( rName ); + } + else + { + // copy storages; object *must* have persistence! + uno::Reference < embed::XStorage > xOld = pImpl->mxStorage->openStorageElement( rName, embed::ElementModes::READ ); + uno::Reference < embed::XStorage > xNew = rCnt.pImpl->mxStorage->openStorageElement( rName, embed::ElementModes::READWRITE ); + xOld->copyToStorage( xNew ); + } + + rCnt.TryToCopyGraphReplacement( *this, rName, rName ); + // RemoveGraphicStream( rName ); + + return true; + } + catch (const uno::Exception&) + { + SAL_WARN( "comphelper.container", "Could not move object!"); + return false; + } + + } + else + SAL_WARN( "comphelper.container", "Unknown object!"); + return false; +} + +// #i119941, bKeepToTempStorage: use to specify whether store the removed object to temporary storage+ +bool EmbeddedObjectContainer::RemoveEmbeddedObject( const uno::Reference < embed::XEmbeddedObject >& xObj, bool bKeepToTempStorage ) +{ + uno::Reference < embed::XEmbedPersist > xPersist( xObj, uno::UNO_QUERY ); + OUString aName; + if ( xPersist.is() ) + aName = xPersist->getEntryName(); + +#if OSL_DEBUG_LEVEL > 1 + uno::Reference < container::XNameAccess > xAccess( pImpl->mxStorage, uno::UNO_QUERY ); + uno::Reference < embed::XLinkageSupport > xLink( xPersist, uno::UNO_QUERY ); + sal_Bool bIsNotEmbedded = !xPersist.is() || ( xLink.is() && xLink->isLink() ); + + // if the object has a persistence and the object is not a link than it must have persistence entry in the storage + OSL_ENSURE( bIsNotEmbedded || xAccess->hasByName(aName), "Removing element not present in storage!" ); +#endif + + // somebody still needs the object, so we must assign a temporary persistence + try + { + if ( xPersist.is() && bKeepToTempStorage ) // #i119941 + { + + if ( !pImpl->mpTempObjectContainer ) + { + pImpl->mpTempObjectContainer = new EmbeddedObjectContainer(); + try + { + // TODO/LATER: in future probably the temporary container will have two storages ( of two formats ) + // the media type will be provided with object insertion + OUString aOrigStorMediaType; + uno::Reference< beans::XPropertySet > xStorProps( pImpl->mxStorage, uno::UNO_QUERY_THROW ); + static constexpr OUString s_sMediaType(u"MediaType"_ustr); + xStorProps->getPropertyValue( s_sMediaType ) >>= aOrigStorMediaType; + + SAL_WARN_IF( aOrigStorMediaType.isEmpty(), "comphelper.container", "No valuable media type in the storage!" ); + + uno::Reference< beans::XPropertySet > xTargetStorProps( + pImpl->mpTempObjectContainer->pImpl->mxStorage, + uno::UNO_QUERY_THROW ); + xTargetStorProps->setPropertyValue( s_sMediaType,uno::Any( aOrigStorMediaType ) ); + } + catch (const uno::Exception&) + { + SAL_WARN( "comphelper.container", "Can not set the new media type to a storage!" ); + } + } + + OUString aTempName, aMediaType; + /* Do not create a new name for a removed object, in the pImpl->mpTempObjectContainer, + because the original m_aEntryName of xObj will be overwritten by InsertEmbeddedObject(), + so uno::Reference < embed::XEmbeddedObject >& xObj will misbehave in + EmbeddedObjectContainer::StoreAsChildren and SfxObjectShell::SaveCompletedChildren + and will throw an exception because of objects with the same names! */ + if( !pImpl->mpTempObjectContainer->HasEmbeddedObject(aName) ) + aTempName = aName; + + pImpl->mpTempObjectContainer->InsertEmbeddedObject( xObj, aTempName ); + + uno::Reference < io::XInputStream > xStream = GetGraphicStream( xObj, &aMediaType ); + if ( xStream.is() ) + pImpl->mpTempObjectContainer->InsertGraphicStream( xStream, aTempName, aMediaType ); + + // object is stored, so at least it can be set to loaded state + xObj->changeState( embed::EmbedStates::LOADED ); + } + else + // objects without persistence need to stay in running state if they shall not be closed + xObj->changeState( embed::EmbedStates::RUNNING ); + } + catch (const uno::Exception&) + { + return false; + } + + auto aIter = std::find_if(pImpl->maNameToObjectMap.begin(), pImpl->maNameToObjectMap.end(), + [&xObj](const EmbeddedObjectContainerNameMap::value_type& rEntry) { return rEntry.second == xObj; }); + if (aIter != pImpl->maNameToObjectMap.end()) + { + pImpl->maObjectToNameMap.erase( aIter->second ); + pImpl->maNameToObjectMap.erase( aIter ); + uno::Reference < container::XChild > xChild( xObj, uno::UNO_QUERY ); + if ( xChild.is() ) + xChild->setParent( uno::Reference < uno::XInterface >() ); + } + else + SAL_WARN( "comphelper.container", "Object not found for removal!" ); + + if ( !xPersist || !bKeepToTempStorage ) // #i119941# + return true; + + // remove replacement image (if there is one) + RemoveGraphicStream( aName ); + + // now it's time to remove the storage from the container storage + try + { +#if OSL_DEBUG_LEVEL > 1 + // if the object has a persistence and the object is not a link than it must have persistence entry in storage + OSL_ENSURE( bIsNotEmbedded || pImpl->mxStorage->hasByName( aName ), "The object has no persistence entry in the storage!" ); +#endif + if ( xPersist.is() && pImpl->mxStorage->hasByName( aName ) ) + pImpl->mxStorage->removeElement( aName ); + } + catch (const uno::Exception&) + { + SAL_WARN( "comphelper.container", "Failed to remove object from storage!" ); + return false; + } + + return true; +} + +void EmbeddedObjectContainer::CloseEmbeddedObject( const uno::Reference < embed::XEmbeddedObject >& xObj ) +{ + // disconnect the object from the container and close it if possible + + auto aIter = std::find_if(pImpl->maNameToObjectMap.begin(), pImpl->maNameToObjectMap.end(), + [&xObj](const EmbeddedObjectContainerNameMap::value_type& rEntry) { return rEntry.second == xObj; }); + if (aIter == pImpl->maNameToObjectMap.end()) + return; + + pImpl->maObjectToNameMap.erase( aIter->second ); + pImpl->maNameToObjectMap.erase( aIter ); + + try + { + xObj->close( true ); + } + catch (const uno::Exception&) + { + // it is no problem if the object is already closed + // TODO/LATER: what if the object can not be closed? + } +} + +uno::Reference < io::XInputStream > EmbeddedObjectContainer::GetGraphicStream( const OUString& aName, OUString* pMediaType ) +{ + uno::Reference < io::XInputStream > xStream; + + SAL_WARN_IF( aName.isEmpty(), "comphelper.container", "Retrieving graphic for unknown object!" ); + if ( !aName.isEmpty() ) + { + try + { + uno::Reference < embed::XStorage > xReplacements = pImpl->GetReplacements(); + uno::Reference < io::XStream > xGraphicStream = xReplacements->openStreamElement( aName, embed::ElementModes::READ ); + xStream = xGraphicStream->getInputStream(); + if ( pMediaType ) + { + uno::Reference < beans::XPropertySet > xSet( xStream, uno::UNO_QUERY ); + if ( xSet.is() ) + { + uno::Any aAny = xSet->getPropertyValue("MediaType"); + aAny >>= *pMediaType; + } + } + } + catch (uno::Exception const& e) + { + SAL_INFO("comphelper.container", + "EmbeddedObjectContainer::GetGraphicStream(): " << e); + } + } + + return xStream; +} + +uno::Reference < io::XInputStream > EmbeddedObjectContainer::GetGraphicStream( const css::uno::Reference < css::embed::XEmbeddedObject >& xObj, OUString* pMediaType ) +{ + // try to load it from the container storage + return GetGraphicStream( GetEmbeddedObjectName( xObj ), pMediaType ); +} + +bool EmbeddedObjectContainer::InsertGraphicStream( const css::uno::Reference < css::io::XInputStream >& rStream, const OUString& rObjectName, const OUString& rMediaType ) +{ + try + { + uno::Reference < embed::XStorage > xReplacements = pImpl->GetReplacements(); + + // store it into the subfolder + uno::Reference < io::XOutputStream > xOutStream; + uno::Reference < io::XStream > xGraphicStream = xReplacements->openStreamElement( rObjectName, + embed::ElementModes::READWRITE | embed::ElementModes::TRUNCATE ); + xOutStream = xGraphicStream->getOutputStream(); + ::comphelper::OStorageHelper::CopyInputToOutput( rStream, xOutStream ); + xOutStream->flush(); + + uno::Reference< beans::XPropertySet > xPropSet( xGraphicStream, uno::UNO_QUERY_THROW ); + + xPropSet->setPropertyValue("UseCommonStoragePasswordEncryption", + uno::Any( true ) ); + xPropSet->setPropertyValue("MediaType", uno::Any(rMediaType) ); + + xPropSet->setPropertyValue("Compressed", + uno::Any( true ) ); + } + catch (const uno::Exception&) + { + return false; + } + + return true; +} + +bool EmbeddedObjectContainer::InsertGraphicStreamDirectly( const css::uno::Reference < css::io::XInputStream >& rStream, const OUString& rObjectName, const OUString& rMediaType ) +{ + try + { + uno::Reference < embed::XStorage > xReplacement = pImpl->GetReplacements(); + uno::Reference < embed::XOptimizedStorage > xOptRepl( xReplacement, uno::UNO_QUERY_THROW ); + + // store it into the subfolder + uno::Sequence< beans::PropertyValue > aProps{ + comphelper::makePropertyValue("MediaType", rMediaType), + comphelper::makePropertyValue("UseCommonStoragePasswordEncryption", true), + comphelper::makePropertyValue("Compressed", true) + }; + + if ( xReplacement->hasByName( rObjectName ) ) + xReplacement->removeElement( rObjectName ); + + xOptRepl->insertStreamElementDirect( rObjectName, rStream, aProps ); + } + catch (const uno::Exception&) + { + return false; + } + + return true; +} + + +void EmbeddedObjectContainer::RemoveGraphicStream( const OUString& rObjectName ) +{ + try + { + uno::Reference < embed::XStorage > xReplacements = pImpl->GetReplacements(); + xReplacements->removeElement( rObjectName ); + } + catch (const uno::Exception&) + { + } +} +namespace { + void InsertStreamIntoPicturesStorage_Impl( const uno::Reference< embed::XStorage >& xDocStor, + const uno::Reference< io::XInputStream >& xInStream, + const OUString& aStreamName ) + { + OSL_ENSURE( !aStreamName.isEmpty() && xInStream.is() && xDocStor.is(), "Misuse of the method!" ); + + try + { + uno::Reference< embed::XStorage > xPictures = xDocStor->openStorageElement( + "Pictures", + embed::ElementModes::READWRITE ); + uno::Reference< io::XStream > xObjReplStr = xPictures->openStreamElement( + aStreamName, + embed::ElementModes::READWRITE | embed::ElementModes::TRUNCATE ); + uno::Reference< io::XOutputStream > xOutStream( + xObjReplStr->getInputStream(), uno::UNO_QUERY_THROW ); + + ::comphelper::OStorageHelper::CopyInputToOutput( xInStream, xOutStream ); + xOutStream->closeOutput(); + + uno::Reference< embed::XTransactedObject > xTransact( xPictures, uno::UNO_QUERY ); + if ( xTransact.is() ) + xTransact->commit(); + } + catch (const uno::Exception&) + { + SAL_WARN( "comphelper.container", "The images storage is not available!" ); + } + } + +} + +bool EmbeddedObjectContainer::StoreAsChildren(bool _bOasisFormat,bool _bCreateEmbedded, bool _bAutoSaveEvent, + const uno::Reference < embed::XStorage >& _xStorage) +{ + bool bResult = false; + try + { + comphelper::EmbeddedObjectContainer aCnt( _xStorage ); + const uno::Sequence < OUString > aNames = GetObjectNames(); + const OUString* pIter = aNames.getConstArray(); + const OUString* pEnd = pIter + aNames.getLength(); + for(;pIter != pEnd;++pIter) + { + uno::Reference < embed::XEmbeddedObject > xObj = GetEmbeddedObject( *pIter ); + SAL_WARN_IF( !xObj.is(), "comphelper.container", "An empty entry in the embedded objects list!" ); + if ( xObj.is() ) + { + bool bSwitchBackToLoaded = false; + uno::Reference< embed::XLinkageSupport > xLink( xObj, uno::UNO_QUERY ); + + uno::Reference < io::XInputStream > xStream; + OUString aMediaType; + + sal_Int32 nCurState = xObj->getCurrentState(); + if ( nCurState == embed::EmbedStates::LOADED || nCurState == embed::EmbedStates::RUNNING ) + { + // means that the object is not active + // copy replacement image from old to new container + xStream = GetGraphicStream( xObj, &aMediaType ); + } + + if ( !xStream.is() && getUserAllowsLinkUpdate() ) + { + // the image must be regenerated + // TODO/LATER: another aspect could be used + if ( xObj->getCurrentState() == embed::EmbedStates::LOADED ) + bSwitchBackToLoaded = true; + + xStream = GetGraphicReplacementStream( + embed::Aspects::MSOLE_CONTENT, + xObj, + &aMediaType ); + } + + if ( _bOasisFormat || (xLink.is() && xLink->isLink()) ) + { + if ( xStream.is() ) + { + if ( _bOasisFormat ) + { + // if it is an embedded object or the optimized inserting fails the normal inserting should be done + if ( _bCreateEmbedded + || !aCnt.InsertGraphicStreamDirectly( xStream, *pIter, aMediaType ) ) + aCnt.InsertGraphicStream( xStream, *pIter, aMediaType ); + } + else + { + // it is a linked object exported into SO7 format + InsertStreamIntoPicturesStorage_Impl( _xStorage, xStream, *pIter ); + } + } + } + + uno::Reference< embed::XEmbedPersist > xPersist( xObj, uno::UNO_QUERY ); + if ( xPersist.is() ) + { + uno::Sequence< beans::PropertyValue > aArgs( _bOasisFormat ? 3 : 4 ); + auto pArgs = aArgs.getArray(); + pArgs[0].Name = "StoreVisualReplacement"; + pArgs[0].Value <<= !_bOasisFormat; + + // if it is an embedded object or the optimized inserting fails the normal inserting should be done + pArgs[1].Name = "CanTryOptimization"; + pArgs[1].Value <<= !_bCreateEmbedded; + + pArgs[2].Name = "AutoSaveEvent"; + pArgs[2].Value <<= _bAutoSaveEvent; + + if ( !_bOasisFormat ) + { + // if object has no cached replacement it will use this one + pArgs[3].Name = "VisualReplacement"; + pArgs[3].Value <<= xStream; + } + + try + { + xPersist->storeAsEntry( _xStorage, xPersist->getEntryName(), uno::Sequence< beans::PropertyValue >(), aArgs ); + } + catch (const embed::WrongStateException&) + { + SAL_WARN("comphelper.container", "failed to store '" << *pIter << "'"); + } + } + + if ( bSwitchBackToLoaded ) + // switch back to loaded state; that way we have a minimum cache confusion + xObj->changeState( embed::EmbedStates::LOADED ); + } + } + + bResult = aCnt.CommitImageSubStorage(); + + } + catch (const uno::Exception& e) + { + // TODO/LATER: error handling + bResult = false; + SAL_WARN("comphelper.container", "failed. Message: " << e); + } + + // the old SO6 format does not store graphical replacements + if ( !_bOasisFormat && bResult ) + { + try + { + // the substorage still can not be locked by the embedded object container + OUString aObjReplElement( "ObjectReplacements" ); + if ( _xStorage->hasByName( aObjReplElement ) && _xStorage->isStorageElement( aObjReplElement ) ) + _xStorage->removeElement( aObjReplElement ); + } + catch (const uno::Exception&) + { + // TODO/LATER: error handling; + bResult = false; + } + } + return bResult; +} + +bool EmbeddedObjectContainer::StoreChildren(bool _bOasisFormat,bool _bObjectsOnly) +{ + bool bResult = true; + const uno::Sequence < OUString > aNames = GetObjectNames(); + const OUString* pIter = aNames.getConstArray(); + const OUString* pEnd = pIter + aNames.getLength(); + for(;pIter != pEnd;++pIter) + { + try + { + uno::Reference < embed::XEmbeddedObject > xObj = GetEmbeddedObject( *pIter ); + SAL_WARN_IF( !xObj.is(), "comphelper.container", "An empty entry in the embedded objects list!" ); + if ( xObj.is() ) + { + sal_Int32 nCurState = xObj->getCurrentState(); + if ( _bOasisFormat && nCurState != embed::EmbedStates::LOADED && nCurState != embed::EmbedStates::RUNNING ) + { + // means that the object is active + // the image must be regenerated + OUString aMediaType; + + // TODO/LATER: another aspect could be used + uno::Reference < io::XInputStream > xStream = + GetGraphicReplacementStream( + embed::Aspects::MSOLE_CONTENT, + xObj, + &aMediaType ); + if ( xStream.is() ) + { + if ( !InsertGraphicStreamDirectly( xStream, *pIter, aMediaType ) ) + InsertGraphicStream( xStream, *pIter, aMediaType ); + } + } + + // TODO/LATER: currently the object by default does not cache replacement image + // that means that if somebody loads SO7 document and store its objects using + // this method the images might be lost. + // Currently this method is only used on storing to alien formats, that means + // that SO7 documents storing does not use it, and all other filters are + // based on OASIS format. But if it changes the method must be fixed. The fix + // must be done only on demand since it can affect performance. + + uno::Reference< embed::XEmbedPersist > xPersist( xObj, uno::UNO_QUERY ); + if ( xPersist.is() ) + { + try + { + //TODO/LATER: only storing if changed! + //xPersist->storeOwn(); //commented, i120168 + + // begin:all charts will be persisted as xml format on disk when saving, which is time consuming. + // '_bObjectsOnly' mean we are storing to alien formats. + // 'isStorageElement' mean current object is NOT a MS OLE format. (may also include in future), i120168 + if (_bObjectsOnly && (nCurState == embed::EmbedStates::LOADED || nCurState == embed::EmbedStates::RUNNING) + && (pImpl->mxStorage->isStorageElement( *pIter ) )) + { + uno::Reference< util::XModifiable > xModifiable( xObj->getComponent(), uno::UNO_QUERY ); + if ( xModifiable.is() && xModifiable->isModified()) + { + xPersist->storeOwn(); + } + else + { + //do nothing. Embedded model is not modified, no need to persist. + } + } + else //the embedded object is in active status, always store back it. + { + xPersist->storeOwn(); + } + //end i120168 + } + catch (const uno::Exception&) + { + // TODO/LATER: error handling + bResult = false; + break; + } + } + + if ( !_bOasisFormat && !_bObjectsOnly ) + { + // copy replacement images for linked objects + try + { + uno::Reference< embed::XLinkageSupport > xLink( xObj, uno::UNO_QUERY ); + if ( xLink.is() && xLink->isLink() ) + { + OUString aMediaType; + uno::Reference < io::XInputStream > xInStream = GetGraphicStream( xObj, &aMediaType ); + if ( xInStream.is() ) + InsertStreamIntoPicturesStorage_Impl( pImpl->mxStorage, xInStream, *pIter ); + } + } + catch (const uno::Exception&) + { + } + } + } + } + catch (const uno::Exception&) + { + // TODO/LATER: error handling + } + } + + if ( bResult && _bOasisFormat ) + bResult = CommitImageSubStorage(); + + if ( bResult && !_bObjectsOnly ) + { + try + { + ReleaseImageSubStorage(); + OUString aObjReplElement( "ObjectReplacements" ); + if ( !_bOasisFormat && pImpl->mxStorage->hasByName( aObjReplElement ) && pImpl->mxStorage->isStorageElement( aObjReplElement ) ) + pImpl->mxStorage->removeElement( aObjReplElement ); + } + catch (const uno::Exception&) + { + // TODO/LATER: error handling + bResult = false; + } + } + return bResult; +} + +uno::Reference< io::XInputStream > EmbeddedObjectContainer::GetGraphicReplacementStream( + sal_Int64 nViewAspect, + const uno::Reference< embed::XEmbeddedObject >& xObj, + OUString* pMediaType ) +{ + uno::Reference< io::XInputStream > xInStream; + if ( xObj.is() ) + { + try + { + // retrieving of the visual representation can switch object to running state + embed::VisualRepresentation aRep = xObj->getPreferredVisualRepresentation( nViewAspect ); + if ( pMediaType ) + *pMediaType = aRep.Flavor.MimeType; + + uno::Sequence < sal_Int8 > aSeq; + aRep.Data >>= aSeq; + xInStream = new ::comphelper::SequenceInputStream( aSeq ); + } + catch (const uno::Exception&) + { + } + } + + return xInStream; +} + +bool EmbeddedObjectContainer::SetPersistentEntries(const uno::Reference< embed::XStorage >& _xStorage,bool _bClearModifiedFlag) +{ + bool bError = false; + const uno::Sequence < OUString > aNames = GetObjectNames(); + const OUString* pIter = aNames.getConstArray(); + const OUString* pEnd = pIter + aNames.getLength(); + for(;pIter != pEnd;++pIter) + { + uno::Reference < embed::XEmbeddedObject > xObj = GetEmbeddedObject( *pIter ); + SAL_WARN_IF( !xObj.is(), "comphelper.container", "An empty entry in the embedded objects list!" ); + if ( xObj.is() ) + { + uno::Reference< embed::XEmbedPersist > xPersist( xObj, uno::UNO_QUERY ); + if ( xPersist.is() ) + { + try + { + xPersist->setPersistentEntry( _xStorage, + *pIter, + embed::EntryInitModes::NO_INIT, + uno::Sequence< beans::PropertyValue >(), + uno::Sequence< beans::PropertyValue >() ); + + } + catch (const uno::Exception&) + { + // TODO/LATER: error handling + bError = true; + break; + } + } + if ( _bClearModifiedFlag ) + { + // if this method is used as part of SaveCompleted the object must stay unmodified after execution + try + { + uno::Reference< util::XModifiable > xModif( xObj->getComponent(), uno::UNO_QUERY_THROW ); + if ( xModif->isModified() ) + xModif->setModified( false ); + } + catch (const uno::Exception&) + { + } + } + } + } + return bError; +} + +bool EmbeddedObjectContainer::getUserAllowsLinkUpdate() const +{ + if (officecfg::Office::Common::Security::Scripting::DisableActiveContent::get()) + return false; + return pImpl->mbUserAllowsLinkUpdate; +} + +void EmbeddedObjectContainer::setUserAllowsLinkUpdate(bool bNew) +{ + if(pImpl->mbUserAllowsLinkUpdate != bNew) + { + pImpl->mbUserAllowsLinkUpdate = bNew; + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/container/enumerablemap.cxx b/comphelper/source/container/enumerablemap.cxx new file mode 100644 index 0000000000..ae78223a20 --- /dev/null +++ b/comphelper/source/container/enumerablemap.cxx @@ -0,0 +1,713 @@ +/* -*- 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 <comphelper/anytostring.hxx> +#include <comphelper/anycompare.hxx> +#include <comphelper/componentbase.hxx> + +#include <com/sun/star/container/XEnumerableMap.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/ucb/AlreadyInitializedException.hpp> +#include <com/sun/star/beans/IllegalTypeException.hpp> +#include <com/sun/star/beans/Pair.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <typelib/typedescription.hxx> + +#include <cmath> +#include <map> +#include <memory> +#include <optional> +#include <utility> + +namespace comphelper +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::RuntimeException; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::Type; + using ::com::sun::star::container::XEnumerableMap; + using ::com::sun::star::lang::NoSupportException; + using ::com::sun::star::beans::IllegalTypeException; + using ::com::sun::star::container::NoSuchElementException; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::lang::XInitialization; + using ::com::sun::star::ucb::AlreadyInitializedException; + using ::com::sun::star::beans::Pair; + using ::com::sun::star::uno::TypeClass; + using ::com::sun::star::uno::TypeClass_VOID; + using ::com::sun::star::uno::TypeClass_UNKNOWN; + using ::com::sun::star::uno::TypeClass_ANY; + using ::com::sun::star::uno::TypeClass_EXCEPTION; + using ::com::sun::star::uno::TypeClass_STRUCT; + using ::com::sun::star::uno::TypeClass_FLOAT; + using ::com::sun::star::uno::TypeClass_DOUBLE; + using ::com::sun::star::uno::TypeClass_INTERFACE; + using ::com::sun::star::lang::XServiceInfo; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::container::XEnumeration; + using ::com::sun::star::uno::TypeDescription; + using ::com::sun::star::lang::DisposedException; + + namespace { + + class MapEnumerator; + + } + + typedef std::map< Any, Any, LessPredicateAdapter > KeyedValues; + + namespace { + + struct MapData + { + Type m_aKeyType; + Type m_aValueType; + std::optional< KeyedValues > m_pValues; + std::shared_ptr< IKeyPredicateLess > m_pKeyCompare; + bool m_bMutable; + std::vector< MapEnumerator* > m_aModListeners; + + MapData() + :m_bMutable( true ) + { + } + + MapData( const MapData& _source ) + :m_aKeyType( _source.m_aKeyType ) + ,m_aValueType( _source.m_aValueType ) + ,m_pKeyCompare( _source.m_pKeyCompare ) + ,m_bMutable( false ) + { + m_pValues.emplace( *_source.m_pValues ); + } + private: + MapData& operator=( const MapData& _source ) = delete; + }; + + } + + static void lcl_registerMapModificationListener( MapData& _mapData, MapEnumerator& _listener ) + { + #if OSL_DEBUG_LEVEL > 0 + for ( const MapEnumerator* lookup : _mapData.m_aModListeners ) + { + OSL_ENSURE( lookup != &_listener, "lcl_registerMapModificationListener: this listener is already registered!" ); + } + #endif + _mapData.m_aModListeners.push_back( &_listener ); + } + + + static void lcl_revokeMapModificationListener( MapData& _mapData, MapEnumerator& _listener ) + { + auto lookup = std::find(_mapData.m_aModListeners.begin(), _mapData.m_aModListeners.end(), &_listener); + if (lookup != _mapData.m_aModListeners.end()) + { + _mapData.m_aModListeners.erase( lookup ); + return; + } + OSL_FAIL( "lcl_revokeMapModificationListener: the listener is not registered!" ); + } + + + static void lcl_notifyMapDataListeners_nothrow( const MapData& _mapData ); + + + // EnumerableMap + + typedef ::cppu::WeakComponentImplHelper < XInitialization + , XEnumerableMap + , XServiceInfo + > Map_IFace; + + namespace { + + class EnumerableMap: public Map_IFace, public ComponentBase + { + public: + EnumerableMap(); + protected: + virtual ~EnumerableMap() override; + + // XInitialization + virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) override; + + // XEnumerableMap + virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createKeyEnumeration( sal_Bool Isolated ) override; + virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createValueEnumeration( sal_Bool Isolated ) override; + virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createElementEnumeration( sal_Bool Isolated ) override; + + // XMap + virtual Type SAL_CALL getKeyType() override; + virtual Type SAL_CALL getValueType() override; + virtual void SAL_CALL clear( ) override; + virtual sal_Bool SAL_CALL containsKey( const Any& _key ) override; + virtual sal_Bool SAL_CALL containsValue( const Any& _value ) override; + virtual Any SAL_CALL get( const Any& _key ) override; + virtual Any SAL_CALL put( const Any& _key, const Any& _value ) override; + virtual Any SAL_CALL remove( const Any& _key ) override; + + // XElementAccess (base of XMap) + virtual Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + private: + void impl_initValues_throw( const Sequence< Pair< Any, Any > >& _initialValues ); + + /// throws an IllegalTypeException if the given value is not compatible with our ValueType + void impl_checkValue_throw( const Any& _value ) const; + void impl_checkKey_throw( const Any& _key ) const; + void impl_checkNaN_throw( const Any& _keyOrValue, const Type& _keyOrValueType ) const; + void impl_checkMutable_throw() const; + + private: + ::osl::Mutex m_aMutex; + MapData m_aData; + }; + + enum EnumerationType + { + eKeys, eValues, eBoth + }; + + class MapEnumerator final + { + public: + MapEnumerator( ::cppu::OWeakObject& _rParent, MapData& _mapData, const EnumerationType _type ) + :m_rParent( _rParent ) + ,m_rMapData( _mapData ) + ,m_eType( _type ) + ,m_mapPos( _mapData.m_pValues->begin() ) + ,m_disposed( false ) + { + lcl_registerMapModificationListener( m_rMapData, *this ); + } + + ~MapEnumerator() + { + dispose(); + } + + void dispose() + { + if ( !m_disposed ) + { + lcl_revokeMapModificationListener( m_rMapData, *this ); + m_disposed = true; + } + } + + // noncopyable + MapEnumerator(const MapEnumerator&) = delete; + const MapEnumerator& operator=(const MapEnumerator&) = delete; + + // XEnumeration equivalents + bool hasMoreElements(); + Any nextElement(); + + /// called when the map was modified + void mapModified(); + + private: + ::cppu::OWeakObject& m_rParent; + MapData& m_rMapData; + const EnumerationType m_eType; + KeyedValues::const_iterator m_mapPos; + bool m_disposed; + }; + + } + + static void lcl_notifyMapDataListeners_nothrow( const MapData& _mapData ) + { + for ( MapEnumerator* loop : _mapData.m_aModListeners ) + { + loop->mapModified(); + } + } + + typedef ::cppu::WeakImplHelper < XEnumeration + > MapEnumeration_Base; + + namespace { + + class MapEnumeration :public ComponentBase + ,public MapEnumeration_Base + { + public: + MapEnumeration( ::cppu::OWeakObject& _parentMap, MapData& _mapData, ::cppu::OBroadcastHelper& _rBHelper, + const EnumerationType _type, const bool _isolated ) + :ComponentBase( _rBHelper, ComponentBase::NoInitializationNeeded() ) + ,m_xKeepMapAlive( _parentMap ) + ,m_pMapDataCopy( _isolated ? new MapData( _mapData ) : nullptr ) + ,m_aEnumerator( *this, _isolated ? *m_pMapDataCopy : _mapData, _type ) + { + } + + // XEnumeration + virtual sal_Bool SAL_CALL hasMoreElements( ) override; + virtual Any SAL_CALL nextElement( ) override; + + protected: + virtual ~MapEnumeration() override + { + acquire(); + { + ::osl::MutexGuard aGuard( getMutex() ); + m_aEnumerator.dispose(); + m_pMapDataCopy.reset(); + } + } + + private: + // since we share our mutex with the main map, we need to keep it alive as long as we live + Reference< XInterface > m_xKeepMapAlive; + std::unique_ptr< MapData > m_pMapDataCopy; + MapEnumerator m_aEnumerator; + }; + + } + + EnumerableMap::EnumerableMap() + :Map_IFace( m_aMutex ) + ,ComponentBase( Map_IFace::rBHelper ) + { + } + + + EnumerableMap::~EnumerableMap() + { + if ( !impl_isDisposed() ) + { + acquire(); + dispose(); + } + } + + + void SAL_CALL EnumerableMap::initialize( const Sequence< Any >& _arguments ) + { + ComponentMethodGuard aGuard( *this, ComponentMethodGuard::MethodType::WithoutInit ); + if ( impl_isInitialized_nothrow() ) + throw AlreadyInitializedException(); + + sal_Int32 nArgumentCount = _arguments.getLength(); + if ( ( nArgumentCount != 2 ) && ( nArgumentCount != 3 ) ) + throw IllegalArgumentException("wrong number of args", static_cast<cppu::OWeakObject*>(this), 1); + + Type aKeyType, aValueType; + if ( !( _arguments[0] >>= aKeyType ) ) + throw IllegalArgumentException("com.sun.star.uno.Type expected.", *this, 1 ); + if ( !( _arguments[1] >>= aValueType ) ) + throw IllegalArgumentException("com.sun.star.uno.Type expected.", *this, 2 ); + + Sequence< Pair< Any, Any > > aInitialValues; + bool bMutable = true; + if ( nArgumentCount == 3 ) + { + if ( !( _arguments[2] >>= aInitialValues ) ) + throw IllegalArgumentException("[]com.sun.star.beans.Pair<any,any> expected.", *this, 2 ); + bMutable = false; + } + + // for the value, anything is allowed, except VOID + if ( ( aValueType.getTypeClass() == TypeClass_VOID ) || ( aValueType.getTypeClass() == TypeClass_UNKNOWN ) ) + throw IllegalTypeException("Unsupported value type.", *this ); + + // create the comparator for the KeyType, and throw if the type is not supported + std::unique_ptr< IKeyPredicateLess > pComparator( getStandardLessPredicate( aKeyType, nullptr ) ); + if (!pComparator) + throw IllegalTypeException("Unsupported key type.", *this ); + + // init members + m_aData.m_aKeyType = aKeyType; + m_aData.m_aValueType = aValueType; + m_aData.m_pKeyCompare = std::move(pComparator); + m_aData.m_pValues.emplace( *m_aData.m_pKeyCompare ); + m_aData.m_bMutable = bMutable; + + if ( aInitialValues.hasElements() ) + impl_initValues_throw( aInitialValues ); + + setInitialized(); + } + + + void EnumerableMap::impl_initValues_throw( const Sequence< Pair< Any, Any > >& _initialValues ) + { + OSL_PRECOND( m_aData.m_pValues && m_aData.m_pValues->empty(), "EnumerableMap::impl_initValues_throw: illegal call!" ); + if (!m_aData.m_pValues || !m_aData.m_pValues->empty()) + throw RuntimeException(); + + const Pair< Any, Any >* mapping = _initialValues.getConstArray(); + const Pair< Any, Any >* mappingEnd = mapping + _initialValues.getLength(); + for ( ; mapping != mappingEnd; ++mapping ) + { + impl_checkValue_throw( mapping->Second ); + (*m_aData.m_pValues)[ mapping->First ] = mapping->Second; + } + } + + + void EnumerableMap::impl_checkValue_throw( const Any& _value ) const + { + if ( !_value.hasValue() ) + // nothing to do, NULL values are always allowed, regardless of the ValueType + return; + + TypeClass eAllowedTypeClass = m_aData.m_aValueType.getTypeClass(); + bool bValid = false; + + switch ( eAllowedTypeClass ) + { + default: + bValid = ( _value.getValueTypeClass() == eAllowedTypeClass ); + break; + case TypeClass_ANY: + bValid = true; + break; + case TypeClass_INTERFACE: + { + // special treatment: _value might contain the proper type, but the interface + // might actually be NULL. Which is still valid ... + if ( m_aData.m_aValueType.isAssignableFrom( _value.getValueType() ) ) + // this also catches the special case where XFoo is our value type, + // and _value contains a NULL-reference to XFoo, or a derived type + bValid = true; + else + { + Reference< XInterface > xValue( _value, UNO_QUERY ); + if ( xValue.is() ) + // XInterface is not-NULL, but is X(ValueType) not-NULL, too? + xValue.set( xValue->queryInterface( m_aData.m_aValueType ), UNO_QUERY ); + bValid = xValue.is(); + } + } + break; + case TypeClass_EXCEPTION: + case TypeClass_STRUCT: + { + // values are accepted if and only if their type equals, or is derived from, our value type + + if ( _value.getValueTypeClass() != eAllowedTypeClass ) + bValid = false; + else + { + const TypeDescription aValueTypeDesc( _value.getValueType() ); + const TypeDescription aRequiredTypeDesc( m_aData.m_aValueType ); + + const _typelib_CompoundTypeDescription* pValueCompoundTypeDesc = + reinterpret_cast< const _typelib_CompoundTypeDescription* >( aValueTypeDesc.get() ); + + while ( pValueCompoundTypeDesc ) + { + if ( typelib_typedescription_equals( &pValueCompoundTypeDesc->aBase, aRequiredTypeDesc.get() ) ) + break; + pValueCompoundTypeDesc = pValueCompoundTypeDesc->pBaseTypeDescription; + } + bValid = ( pValueCompoundTypeDesc != nullptr ); + } + } + break; + } + + if ( !bValid ) + { + throw IllegalTypeException( + "Incompatible value type. Found '" + _value.getValueTypeName() + + "', where '" + m_aData.m_aValueType.getTypeName() + + "' (or compatible type) is expected.", + *const_cast< EnumerableMap* >( this ) ); + } + + impl_checkNaN_throw( _value, m_aData.m_aValueType ); + } + + + void EnumerableMap::impl_checkNaN_throw( const Any& _keyOrValue, const Type& _keyOrValueType ) const + { + if ( ( _keyOrValueType.getTypeClass() == TypeClass_DOUBLE ) + || ( _keyOrValueType.getTypeClass() == TypeClass_FLOAT ) + ) + { + double nValue(0); + if ( _keyOrValue >>= nValue ) + if ( std::isnan( nValue ) ) + throw IllegalArgumentException( + "NaN (not-a-number) not supported by this implementation.", + *const_cast< EnumerableMap* >( this ), 0 ); + // (note that the case of _key not containing a float/double value is handled in the + // respective IKeyPredicateLess implementation, so there's no need to handle this here.) + } + } + + + void EnumerableMap::impl_checkKey_throw( const Any& _key ) const + { + if ( !_key.hasValue() ) + throw IllegalArgumentException( + "NULL keys not supported by this implementation.", + *const_cast< EnumerableMap* >( this ), 0 ); + + impl_checkNaN_throw( _key, m_aData.m_aKeyType ); + } + + + void EnumerableMap::impl_checkMutable_throw() const + { + if ( !m_aData.m_bMutable ) + throw NoSupportException( + "The map is immutable.", + *const_cast< EnumerableMap* >( this ) ); + } + + + Reference< XEnumeration > SAL_CALL EnumerableMap::createKeyEnumeration( sal_Bool Isolated ) + { + ComponentMethodGuard aGuard( *this ); + return new MapEnumeration( *this, m_aData, getBroadcastHelper(), eKeys, Isolated ); + } + + + Reference< XEnumeration > SAL_CALL EnumerableMap::createValueEnumeration( sal_Bool Isolated ) + { + ComponentMethodGuard aGuard( *this ); + return new MapEnumeration( *this, m_aData, getBroadcastHelper(), eValues, Isolated ); + } + + + Reference< XEnumeration > SAL_CALL EnumerableMap::createElementEnumeration( sal_Bool Isolated ) + { + ComponentMethodGuard aGuard( *this ); + return new MapEnumeration( *this, m_aData, getBroadcastHelper(), eBoth, Isolated ); + } + + + Type SAL_CALL EnumerableMap::getKeyType() + { + ComponentMethodGuard aGuard( *this ); + return m_aData.m_aKeyType; + } + + + Type SAL_CALL EnumerableMap::getValueType() + { + ComponentMethodGuard aGuard( *this ); + return m_aData.m_aValueType; + } + + + void SAL_CALL EnumerableMap::clear( ) + { + ComponentMethodGuard aGuard( *this ); + impl_checkMutable_throw(); + + m_aData.m_pValues->clear(); + + lcl_notifyMapDataListeners_nothrow( m_aData ); + } + + + sal_Bool SAL_CALL EnumerableMap::containsKey( const Any& _key ) + { + ComponentMethodGuard aGuard( *this ); + impl_checkKey_throw( _key ); + + KeyedValues::const_iterator pos = m_aData.m_pValues->find( _key ); + return ( pos != m_aData.m_pValues->end() ); + } + + + sal_Bool SAL_CALL EnumerableMap::containsValue( const Any& _value ) + { + ComponentMethodGuard aGuard( *this ); + impl_checkValue_throw( _value ); + for (auto const& value : *m_aData.m_pValues) + { + if ( value.second == _value ) + return true; + } + return false; + } + + + Any SAL_CALL EnumerableMap::get( const Any& _key ) + { + ComponentMethodGuard aGuard( *this ); + impl_checkKey_throw( _key ); + + KeyedValues::const_iterator pos = m_aData.m_pValues->find( _key ); + if ( pos == m_aData.m_pValues->end() ) + throw NoSuchElementException( anyToString( _key ), *this ); + + return pos->second; + } + + + Any SAL_CALL EnumerableMap::put( const Any& _key, const Any& _value ) + { + ComponentMethodGuard aGuard( *this ); + impl_checkMutable_throw(); + impl_checkKey_throw( _key ); + impl_checkValue_throw( _value ); + + Any previousValue; + + KeyedValues::iterator pos = m_aData.m_pValues->find( _key ); + if ( pos != m_aData.m_pValues->end() ) + { + previousValue = pos->second; + pos->second = _value; + } + else + { + (*m_aData.m_pValues)[ _key ] = _value; + } + + lcl_notifyMapDataListeners_nothrow( m_aData ); + + return previousValue; + } + + + Any SAL_CALL EnumerableMap::remove( const Any& _key ) + { + ComponentMethodGuard aGuard( *this ); + impl_checkMutable_throw(); + impl_checkKey_throw( _key ); + + Any previousValue; + + KeyedValues::iterator pos = m_aData.m_pValues->find( _key ); + if ( pos != m_aData.m_pValues->end() ) + { + previousValue = pos->second; + m_aData.m_pValues->erase( pos ); + } + + lcl_notifyMapDataListeners_nothrow( m_aData ); + + return previousValue; + } + + + Type SAL_CALL EnumerableMap::getElementType() + { + return ::cppu::UnoType< Pair< Any, Any > >::get(); + } + + + sal_Bool SAL_CALL EnumerableMap::hasElements() + { + ComponentMethodGuard aGuard( *this ); + return m_aData.m_pValues->empty(); + } + + + OUString SAL_CALL EnumerableMap::getImplementationName( ) + { + return "org.openoffice.comp.comphelper.EnumerableMap"; + } + + sal_Bool SAL_CALL EnumerableMap::supportsService( const OUString& _serviceName ) + { + return cppu::supportsService(this, _serviceName); + } + + + Sequence< OUString > SAL_CALL EnumerableMap::getSupportedServiceNames( ) + { + return { "com.sun.star.container.EnumerableMap" }; + } + + bool MapEnumerator::hasMoreElements() + { + if ( m_disposed ) + throw DisposedException( OUString(), m_rParent ); + return m_mapPos != m_rMapData.m_pValues->end(); + } + + + Any MapEnumerator::nextElement() + { + if ( m_disposed ) + throw DisposedException( OUString(), m_rParent ); + if ( m_mapPos == m_rMapData.m_pValues->end() ) + throw NoSuchElementException("No more elements.", m_rParent ); + + Any aNextElement; + switch ( m_eType ) + { + case eKeys: aNextElement = m_mapPos->first; break; + case eValues: aNextElement = m_mapPos->second; break; + case eBoth: aNextElement <<= Pair< Any, Any >( m_mapPos->first, m_mapPos->second ); break; + } + ++m_mapPos; + return aNextElement; + } + + + void MapEnumerator::mapModified() + { + m_disposed = true; + } + + + sal_Bool SAL_CALL MapEnumeration::hasMoreElements( ) + { + ComponentMethodGuard aGuard( *this ); + return m_aEnumerator.hasMoreElements(); + } + + + Any SAL_CALL MapEnumeration::nextElement( ) + { + ComponentMethodGuard aGuard( *this ); + return m_aEnumerator.nextElement(); + } + + +} // namespace comphelper + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +org_openoffice_comp_comphelper_EnumerableMap( + css::uno::XComponentContext*, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new comphelper::EnumerableMap()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/container/enumhelper.cxx b/comphelper/source/container/enumhelper.cxx new file mode 100644 index 0000000000..66ba151982 --- /dev/null +++ b/comphelper/source/container/enumhelper.cxx @@ -0,0 +1,280 @@ +/* -*- 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 <comphelper/enumhelper.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <utility> + +namespace comphelper +{ + +OEnumerationByName::OEnumerationByName(css::uno::Reference<css::container::XNameAccess> _xAccess) + :m_aNames(_xAccess->getElementNames()) + ,m_xAccess(_xAccess) + ,m_nPos(0) + ,m_bListening(false) +{ + impl_startDisposeListening(); +} + + +OEnumerationByName::OEnumerationByName(css::uno::Reference<css::container::XNameAccess> _xAccess, + std::vector<OUString> _aNames ) + :m_aNames(std::move(_aNames)) + ,m_xAccess(std::move(_xAccess)) + ,m_nPos(0) + ,m_bListening(false) +{ + impl_startDisposeListening(); +} + +OEnumerationByName::~OEnumerationByName() +{ + std::lock_guard aLock(m_aLock); + + impl_stopDisposeListening(); +} + + +sal_Bool SAL_CALL OEnumerationByName::hasMoreElements( ) +{ + std::lock_guard aLock(m_aLock); + + if (m_xAccess.is() && getLength() > m_nPos) + return true; + + if (m_xAccess.is()) + { + impl_stopDisposeListening(); + m_xAccess.clear(); + } + + return false; +} + + +css::uno::Any SAL_CALL OEnumerationByName::nextElement( ) +{ + std::lock_guard aLock(m_aLock); + + css::uno::Any aRes; + if (m_xAccess.is() && m_nPos < getLength()) + aRes = m_xAccess->getByName(getElement(m_nPos++)); + + if (m_xAccess.is() && m_nPos >= getLength()) + { + impl_stopDisposeListening(); + m_xAccess.clear(); + } + + if (!aRes.hasValue()) //There are no more elements + throw css::container::NoSuchElementException(); + + return aRes; +} + +void SAL_CALL OEnumerationByName::disposing(const css::lang::EventObject& aEvent) +{ + std::lock_guard aLock(m_aLock); + + if (aEvent.Source == m_xAccess) + m_xAccess.clear(); +} + + +void OEnumerationByName::impl_startDisposeListening() +{ + if (m_bListening) + return; + + osl_atomic_increment(&m_refCount); + css::uno::Reference< css::lang::XComponent > xDisposable(m_xAccess, css::uno::UNO_QUERY); + if (xDisposable.is()) + { + xDisposable->addEventListener(this); + m_bListening = true; + } + osl_atomic_decrement(&m_refCount); +} + + +void OEnumerationByName::impl_stopDisposeListening() +{ + if (!m_bListening) + return; + + osl_atomic_increment(&m_refCount); + css::uno::Reference< css::lang::XComponent > xDisposable(m_xAccess, css::uno::UNO_QUERY); + if (xDisposable.is()) + { + xDisposable->removeEventListener(this); + m_bListening = false; + } + osl_atomic_decrement(&m_refCount); +} + +sal_Int32 OEnumerationByName::getLength() const +{ + if (m_aNames.index() == 0) + return std::get<css::uno::Sequence<OUString>>(m_aNames).getLength(); + else + return std::get<std::vector<OUString>>(m_aNames).size(); +} + +const OUString& OEnumerationByName::getElement(sal_Int32 nIndex) const +{ + if (m_aNames.index() == 0) + return std::get<css::uno::Sequence<OUString>>(m_aNames).getConstArray()[nIndex]; + else + return std::get<std::vector<OUString>>(m_aNames)[nIndex]; +} + + +OEnumerationByIndex::OEnumerationByIndex(css::uno::Reference< css::container::XIndexAccess > _xAccess) + :m_xAccess(std::move(_xAccess)) + ,m_nPos(0) + ,m_bListening(false) +{ + impl_startDisposeListening(); +} + + +OEnumerationByIndex::~OEnumerationByIndex() +{ + std::lock_guard aLock(m_aLock); + + impl_stopDisposeListening(); +} + + +sal_Bool SAL_CALL OEnumerationByIndex::hasMoreElements( ) +{ + std::lock_guard aLock(m_aLock); + + if (m_xAccess.is() && m_xAccess->getCount() > m_nPos) + return true; + + if (m_xAccess.is()) + { + impl_stopDisposeListening(); + m_xAccess.clear(); + } + + return false; +} + + +css::uno::Any SAL_CALL OEnumerationByIndex::nextElement( ) +{ + std::lock_guard aLock(m_aLock); + + css::uno::Any aRes; + if (m_xAccess.is() && m_nPos < m_xAccess->getCount()) + aRes = m_xAccess->getByIndex(m_nPos++); + + if (m_xAccess.is() && m_nPos >= m_xAccess->getCount()) + { + impl_stopDisposeListening(); + m_xAccess.clear(); + } + + if (!aRes.hasValue()) + throw css::container::NoSuchElementException(); + return aRes; +} + + +void SAL_CALL OEnumerationByIndex::disposing(const css::lang::EventObject& aEvent) +{ + std::lock_guard aLock(m_aLock); + + if (aEvent.Source == m_xAccess) + m_xAccess.clear(); +} + + +void OEnumerationByIndex::impl_startDisposeListening() +{ + if (m_bListening) + return; + + osl_atomic_increment(&m_refCount); + css::uno::Reference< css::lang::XComponent > xDisposable(m_xAccess, css::uno::UNO_QUERY); + if (xDisposable.is()) + { + xDisposable->addEventListener(this); + m_bListening = true; + } + osl_atomic_decrement(&m_refCount); +} + + +void OEnumerationByIndex::impl_stopDisposeListening() +{ + if (!m_bListening) + return; + + osl_atomic_increment(&m_refCount); + css::uno::Reference< css::lang::XComponent > xDisposable(m_xAccess, css::uno::UNO_QUERY); + if (xDisposable.is()) + { + xDisposable->removeEventListener(this); + m_bListening = false; + } + osl_atomic_decrement(&m_refCount); +} + +OAnyEnumeration::OAnyEnumeration(const css::uno::Sequence< css::uno::Any >& lItems) + :m_nPos(0) + ,m_lItems(lItems) +{ +} + + +OAnyEnumeration::~OAnyEnumeration() +{ +} + + +sal_Bool SAL_CALL OAnyEnumeration::hasMoreElements( ) +{ + std::lock_guard aLock(m_aLock); + + return (m_lItems.getLength() > m_nPos); +} + + +css::uno::Any SAL_CALL OAnyEnumeration::nextElement( ) +{ + if ( ! hasMoreElements()) + throw css::container::NoSuchElementException(); + + std::lock_guard aLock(m_aLock); + sal_Int32 nPos = m_nPos; + ++m_nPos; + return m_lItems[nPos]; +} + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/container/interfacecontainer2.cxx b/comphelper/source/container/interfacecontainer2.cxx new file mode 100644 index 0000000000..df1c9e0e69 --- /dev/null +++ b/comphelper/source/container/interfacecontainer2.cxx @@ -0,0 +1,424 @@ +/* -*- 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 <comphelper/interfacecontainer2.hxx> +#include <comphelper/multicontainer2.hxx> + +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <osl/mutex.hxx> + +#include <memory> + +#include <com/sun/star/lang/XEventListener.hpp> + + +using namespace osl; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; + +namespace comphelper +{ + +OInterfaceIteratorHelper2::OInterfaceIteratorHelper2( OInterfaceContainerHelper2 & rCont_ ) + : rCont( rCont_ ) +{ + MutexGuard aGuard( rCont.rMutex ); + if( rCont.bInUse ) + // worst case, two iterators at the same time + rCont.copyAndResetInUse(); + bIsList = rCont_.bIsList; + aData = rCont_.aData; + if( bIsList ) + { + rCont.bInUse = true; + nRemain = aData.pAsVector->size(); + } + else if( aData.pAsInterface ) + { + aData.pAsInterface->acquire(); + nRemain = 1; + } + else + nRemain = 0; +} + +OInterfaceIteratorHelper2::~OInterfaceIteratorHelper2() +{ + bool bShared; + { + MutexGuard aGuard( rCont.rMutex ); + // bResetInUse protect the iterator against recursion + bShared = aData.pAsVector == rCont.aData.pAsVector && rCont.bIsList; + if( bShared ) + { + OSL_ENSURE( rCont.bInUse, "OInterfaceContainerHelper2 must be in use" ); + rCont.bInUse = false; + } + } + + if( !bShared ) + { + if( bIsList ) + // Sequence owned by the iterator + delete aData.pAsVector; + else if( aData.pAsInterface ) + // Interface is acquired by the iterator + aData.pAsInterface->release(); + } +} + +XInterface * OInterfaceIteratorHelper2::next() +{ + if( nRemain ) + { + nRemain--; + if( bIsList ) + return (*aData.pAsVector)[nRemain].get(); + else if( aData.pAsInterface ) + return aData.pAsInterface; + } + // exception + return nullptr; +} + +void OInterfaceIteratorHelper2::remove() +{ + if( bIsList ) + { + OSL_ASSERT( nRemain >= 0 && + o3tl::make_unsigned(nRemain) < aData.pAsVector->size() ); + rCont.removeInterface( (*aData.pAsVector)[nRemain] ); + } + else + { + OSL_ASSERT( 0 == nRemain ); + rCont.removeInterface( aData.pAsInterface ); + } +} + +OInterfaceContainerHelper2::OInterfaceContainerHelper2( Mutex & rMutex_ ) + : rMutex( rMutex_ ) + , bInUse( false ) + , bIsList( false ) +{ +} + +OInterfaceContainerHelper2::~OInterfaceContainerHelper2() +{ + OSL_ENSURE( !bInUse, "~OInterfaceContainerHelper2 but is in use" ); + if( bIsList ) + delete aData.pAsVector; + else if( aData.pAsInterface ) + aData.pAsInterface->release(); +} + +sal_Int32 OInterfaceContainerHelper2::getLength() const +{ + MutexGuard aGuard( rMutex ); + if( bIsList ) + return aData.pAsVector->size(); + else if( aData.pAsInterface ) + return 1; + return 0; +} + +std::vector< Reference<XInterface> > OInterfaceContainerHelper2::getElements() const +{ + std::vector< Reference<XInterface> > rVec; + MutexGuard aGuard( rMutex ); + if( bIsList ) + rVec = *aData.pAsVector; + else if( aData.pAsInterface ) + { + rVec.emplace_back( aData.pAsInterface ); + } + return rVec; +} + +void OInterfaceContainerHelper2::copyAndResetInUse() +{ + OSL_ENSURE( bInUse, "OInterfaceContainerHelper2 not in use" ); + if( bInUse ) + { + // this should be the worst case. If an iterator is active + // and a new Listener is added. + if( bIsList ) + aData.pAsVector = new std::vector< Reference< XInterface > >( *aData.pAsVector ); + else if( aData.pAsInterface ) + aData.pAsInterface->acquire(); + + bInUse = false; + } +} + +sal_Int32 OInterfaceContainerHelper2::addInterface( const Reference<XInterface> & rListener ) +{ + OSL_ASSERT( rListener.is() ); + if ( !rListener.is() ) + return 0; + + MutexGuard aGuard( rMutex ); + if( bInUse ) + copyAndResetInUse(); + + if( bIsList ) + { + aData.pAsVector->push_back( rListener ); + return aData.pAsVector->size(); + } + else if( aData.pAsInterface ) + { + std::vector< Reference< XInterface > > * pVec = new std::vector< Reference< XInterface > >( 2 ); + (*pVec)[0] = aData.pAsInterface; + (*pVec)[1] = rListener; + aData.pAsInterface->release(); + aData.pAsVector = pVec; + bIsList = true; + return 2; + } + else + { + aData.pAsInterface = rListener.get(); + if( rListener.is() ) + rListener->acquire(); + return 1; + } +} + +sal_Int32 OInterfaceContainerHelper2::removeInterface( const Reference<XInterface> & rListener ) +{ + OSL_ASSERT( rListener.is() ); + MutexGuard aGuard( rMutex ); + if( bInUse ) + copyAndResetInUse(); + + if( bIsList ) + { + // It is not valid to compare the pointer directly, but it's faster. + auto it = std::find_if(aData.pAsVector->begin(), aData.pAsVector->end(), + [&rListener](const css::uno::Reference<css::uno::XInterface>& rItem) { + return rItem.get() == rListener.get(); }); + + // interface not found, use the correct compare method + if (it == aData.pAsVector->end()) + it = std::find(aData.pAsVector->begin(), aData.pAsVector->end(), rListener); + + if (it != aData.pAsVector->end()) + aData.pAsVector->erase(it); + + if( aData.pAsVector->size() == 1 ) + { + XInterface * p = (*aData.pAsVector)[0].get(); + p->acquire(); + delete aData.pAsVector; + aData.pAsInterface = p; + bIsList = false; + return 1; + } + else + return aData.pAsVector->size(); + } + else if( aData.pAsInterface && Reference<XInterface>( aData.pAsInterface ) == rListener ) + { + aData.pAsInterface->release(); + aData.pAsInterface = nullptr; + } + return aData.pAsInterface ? 1 : 0; +} + +Reference<XInterface> OInterfaceContainerHelper2::getInterface( sal_Int32 nIndex ) const +{ + MutexGuard aGuard( rMutex ); + + if( bIsList ) + return (*aData.pAsVector)[nIndex]; + else if( aData.pAsInterface ) + { + if (nIndex == 0) + return aData.pAsInterface; + } + throw std::out_of_range("index out of range"); +} + +void OInterfaceContainerHelper2::disposeAndClear( const EventObject & rEvt ) +{ + ClearableMutexGuard aGuard( rMutex ); + OInterfaceIteratorHelper2 aIt( *this ); + // Release container, in case new entries come while disposing + OSL_ENSURE( !bIsList || bInUse, "OInterfaceContainerHelper2 not in use" ); + if( !bIsList && aData.pAsInterface ) + aData.pAsInterface->release(); + // set the member to null, use the iterator to delete the values + aData.pAsInterface = nullptr; + bIsList = false; + bInUse = false; + aGuard.clear(); + while( aIt.hasMoreElements() ) + { + try + { + Reference<XEventListener > xLst( aIt.next(), UNO_QUERY ); + if( xLst.is() ) + xLst->disposing( rEvt ); + } + catch ( RuntimeException & ) + { + // be robust, if e.g. a remote bridge has disposed already. + // there is no way to delegate the error to the caller :o(. + } + } +} + + +void OInterfaceContainerHelper2::clear() +{ + MutexGuard aGuard( rMutex ); + // Release container, in case new entries come while disposing + OSL_ENSURE( !bIsList || bInUse, "OInterfaceContainerHelper2 not in use" ); + if (bInUse) + copyAndResetInUse(); + if (bIsList) + delete aData.pAsVector; + else if (aData.pAsInterface) + aData.pAsInterface->release(); + aData.pAsInterface = nullptr; + bIsList = false; +} + + + +// specialized class for type + +OMultiTypeInterfaceContainerHelper2::OMultiTypeInterfaceContainerHelper2( Mutex & rMutex_ ) + : rMutex( rMutex_ ) +{ +} + +OMultiTypeInterfaceContainerHelper2::~OMultiTypeInterfaceContainerHelper2() +{ +} + +std::vector< css::uno::Type > OMultiTypeInterfaceContainerHelper2::getContainedTypes() const +{ + ::osl::MutexGuard aGuard( rMutex ); + std::vector< Type > aInterfaceTypes; + aInterfaceTypes.reserve( m_aMap.size() ); + for (const auto& rItem : m_aMap) + { + // are interfaces added to this container? + if( rItem.second->getLength() ) + // yes, put the type in the array + aInterfaceTypes.push_back(rItem.first); + } + return aInterfaceTypes; +} + +OMultiTypeInterfaceContainerHelper2::t_type2ptr::iterator OMultiTypeInterfaceContainerHelper2::findType(const Type & rKey ) +{ + return std::find_if(m_aMap.begin(), m_aMap.end(), + [&rKey](const t_type2ptr::value_type& rItem) { return rItem.first == rKey; }); +} + +OMultiTypeInterfaceContainerHelper2::t_type2ptr::const_iterator OMultiTypeInterfaceContainerHelper2::findType(const Type & rKey ) const +{ + return std::find_if(m_aMap.begin(), m_aMap.end(), + [&rKey](const t_type2ptr::value_type& rItem) { return rItem.first == rKey; }); +} + +OInterfaceContainerHelper2 * OMultiTypeInterfaceContainerHelper2::getContainer( const Type & rKey ) const +{ + ::osl::MutexGuard aGuard( rMutex ); + + auto iter = findType( rKey ); + if( iter != m_aMap.end() ) + return (*iter).second.get(); + return nullptr; +} + +sal_Int32 OMultiTypeInterfaceContainerHelper2::addInterface( + const Type & rKey, const Reference< XInterface > & rListener ) +{ + ::osl::MutexGuard aGuard( rMutex ); + auto iter = findType( rKey ); + if( iter == m_aMap.end() ) + { + OInterfaceContainerHelper2 * pLC = new OInterfaceContainerHelper2( rMutex ); + m_aMap.emplace_back(rKey, pLC); + return pLC->addInterface( rListener ); + } + return (*iter).second->addInterface( rListener ); +} + +sal_Int32 OMultiTypeInterfaceContainerHelper2::removeInterface( + const Type & rKey, const Reference< XInterface > & rListener ) +{ + ::osl::MutexGuard aGuard( rMutex ); + + // search container with id nUik + auto iter = findType( rKey ); + // container found? + if( iter != m_aMap.end() ) + return (*iter).second->removeInterface( rListener ); + + // no container with this id. Always return 0 + return 0; +} + +void OMultiTypeInterfaceContainerHelper2::disposeAndClear( const EventObject & rEvt ) +{ + t_type2ptr::size_type nSize = 0; + std::unique_ptr<OInterfaceContainerHelper2 *[]> ppListenerContainers; + { + ::osl::MutexGuard aGuard( rMutex ); + nSize = m_aMap.size(); + if( nSize ) + { + typedef OInterfaceContainerHelper2* ppp; + ppListenerContainers.reset(new ppp[nSize]); + + t_type2ptr::size_type i = 0; + for (const auto& rItem : m_aMap) + { + ppListenerContainers[i++] = rItem.second.get(); + } + } + } + + // create a copy, because do not fire event in a guarded section + for( t_type2ptr::size_type i = 0; i < nSize; i++ ) + { + if( ppListenerContainers[i] ) + ppListenerContainers[i]->disposeAndClear( rEvt ); + } +} + +void OMultiTypeInterfaceContainerHelper2::clear() +{ + ::osl::MutexGuard aGuard( rMutex ); + + for (auto& rItem : m_aMap) + rItem.second->clear(); +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/container/namecontainer.cxx b/comphelper/source/container/namecontainer.cxx new file mode 100644 index 0000000000..c13ee7486e --- /dev/null +++ b/comphelper/source/container/namecontainer.cxx @@ -0,0 +1,166 @@ +/* -*- 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 <sal/config.h> + +#include <map> +#include <mutex> + +#include <comphelper/namecontainer.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/implbase.hxx> +#include <osl/mutex.hxx> +#include <com/sun/star/container/XNameContainer.hpp> + +typedef std::map<OUString, css::uno::Any> SvGenericNameContainerMapImpl; + +namespace comphelper +{ + namespace { + + /** this is the base helper class for NameContainer that's also declared in this header. */ + class NameContainer : public ::cppu::WeakImplHelper< css::container::XNameContainer > + { + public: + explicit NameContainer( const css::uno::Type& aType ); + + // XNameContainer + virtual void SAL_CALL insertByName( const OUString& aName, const css::uno::Any& aElement ) override; + virtual void SAL_CALL removeByName( const OUString& Name ) override; + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& aName, const css::uno::Any& aElement ) override; + + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XElementAccess + virtual sal_Bool SAL_CALL hasElements( ) override; + virtual css::uno::Type SAL_CALL getElementType( ) override; + + private: + SvGenericNameContainerMapImpl maProperties; + const css::uno::Type maType; + std::mutex maMutex; + }; + + } +} + +using namespace ::comphelper; +using namespace ::osl; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; + + +NameContainer::NameContainer( const css::uno::Type& aType ) +: maType( aType ) +{ +} + +// XNameContainer +void SAL_CALL NameContainer::insertByName( const OUString& aName, const Any& aElement ) +{ + std::scoped_lock aGuard( maMutex ); + + if( maProperties.find( aName ) != maProperties.end() ) + throw ElementExistException(); + + if( aElement.getValueType() != maType ) + throw IllegalArgumentException("element is wrong type", static_cast<cppu::OWeakObject*>(this), 2); + + maProperties.emplace(aName,aElement); +} + +void SAL_CALL NameContainer::removeByName( const OUString& Name ) +{ + std::scoped_lock aGuard( maMutex ); + + SvGenericNameContainerMapImpl::iterator aIter = maProperties.find( Name ); + if( aIter == maProperties.end() ) + throw NoSuchElementException(); + + maProperties.erase( aIter ); +} + +// XNameReplace + +void SAL_CALL NameContainer::replaceByName( const OUString& aName, const Any& aElement ) +{ + std::scoped_lock aGuard( maMutex ); + + SvGenericNameContainerMapImpl::iterator aIter( maProperties.find( aName ) ); + if( aIter == maProperties.end() ) + throw NoSuchElementException(); + + if( aElement.getValueType() != maType ) + throw IllegalArgumentException("element is wrong type", static_cast<cppu::OWeakObject*>(this), 2); + + (*aIter).second = aElement; +} + +// XNameAccess + +Any SAL_CALL NameContainer::getByName( const OUString& aName ) +{ + std::scoped_lock aGuard( maMutex ); + + SvGenericNameContainerMapImpl::iterator aIter = maProperties.find( aName ); + if( aIter == maProperties.end() ) + throw NoSuchElementException(); + + return (*aIter).second; +} + +Sequence< OUString > SAL_CALL NameContainer::getElementNames( ) +{ + std::scoped_lock aGuard( maMutex ); + + return comphelper::mapKeysToSequence(maProperties); +} + +sal_Bool SAL_CALL NameContainer::hasByName( const OUString& aName ) +{ + std::scoped_lock aGuard( maMutex ); + + SvGenericNameContainerMapImpl::iterator aIter = maProperties.find( aName ); + return aIter != maProperties.end(); +} + +sal_Bool SAL_CALL NameContainer::hasElements( ) +{ + std::scoped_lock aGuard( maMutex ); + + return !maProperties.empty(); +} + +Type SAL_CALL NameContainer::getElementType() +{ + return maType; +} + +Reference< XNameContainer > comphelper::NameContainer_createInstance( const Type& aType ) +{ + return new NameContainer(aType); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |