diff options
Diffstat (limited to 'comphelper/source')
107 files changed, 29092 insertions, 0 deletions
diff --git a/comphelper/source/compare/AnyCompareFactory.cxx b/comphelper/source/compare/AnyCompareFactory.cxx new file mode 100644 index 000000000..40e5f0806 --- /dev/null +++ b/comphelper/source/compare/AnyCompareFactory.cxx @@ -0,0 +1,136 @@ +/* -*- 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/ucb/XAnyCompareFactory.hpp> +#include <com/sun/star/i18n/Collator.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/uno/Sequence.h> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> + +using namespace com::sun::star::uno; +using namespace com::sun::star::ucb; +using namespace com::sun::star::lang; +using namespace com::sun::star::i18n; + +namespace { + +class AnyCompare : public ::cppu::WeakImplHelper< XAnyCompare > +{ + Reference< XCollator > m_xCollator; + +public: + AnyCompare( Reference< XComponentContext > const & xContext, const Locale& rLocale ) + : m_xCollator(Collator::create( xContext )) + { + m_xCollator->loadDefaultCollator( rLocale, + 0 ); //??? + } + + virtual sal_Int16 SAL_CALL compare( const Any& any1, const Any& any2 ) override; +}; + +class AnyCompareFactory : public cppu::WeakImplHelper< XAnyCompareFactory, XInitialization, XServiceInfo > +{ + Reference< XAnyCompare > m_xAnyCompare; + Reference< XComponentContext > m_xContext; + Locale m_Locale; + +public: + explicit AnyCompareFactory( Reference< XComponentContext > const & xContext ) : m_xContext( xContext ) + {} + + // XAnyCompareFactory + virtual Reference< XAnyCompare > SAL_CALL createAnyCompareByName ( const OUString& aPropertyName ) override; + + // XInitialization + virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) 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; +}; + +} + +sal_Int16 SAL_CALL AnyCompare::compare( const Any& any1, const Any& any2 ) +{ + sal_Int16 aResult = 0; + + OUString aStr1; + OUString aStr2; + + any1 >>= aStr1; + any2 >>= aStr2; + + aResult = static_cast<sal_Int16>(m_xCollator->compareString(aStr1, aStr2)); + + return aResult; +} + +Reference< XAnyCompare > SAL_CALL AnyCompareFactory::createAnyCompareByName( const OUString& aPropertyName ) +{ + // for now only OUString properties compare is implemented + // so no check for the property name is done + + if( aPropertyName == "Title" ) + return m_xAnyCompare; + + return Reference< XAnyCompare >(); +} + +void SAL_CALL AnyCompareFactory::initialize( const Sequence< Any >& aArguments ) +{ + if( aArguments.hasElements() ) + { + if( aArguments[0] >>= m_Locale ) + { + m_xAnyCompare = new AnyCompare( m_xContext, m_Locale ); + return; + } + } +} + +OUString SAL_CALL AnyCompareFactory::getImplementationName( ) +{ + return "AnyCompareFactory"; +} + +sal_Bool SAL_CALL AnyCompareFactory::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL AnyCompareFactory::getSupportedServiceNames( ) +{ + return { "com.sun.star.ucb.AnyCompareFactory" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +AnyCompareFactory_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new AnyCompareFactory(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/container/IndexedPropertyValuesContainer.cxx b/comphelper/source/container/IndexedPropertyValuesContainer.cxx new file mode 100644 index 000000000..f5b7358d6 --- /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 000000000..a44837f11 --- /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 000000000..7b2432723 --- /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 000000000..c687e7279 --- /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 000000000..094597fe6 --- /dev/null +++ b/comphelper/source/container/embeddedobjectcontainer.cxx @@ -0,0 +1,1508 @@ +/* -*- 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 <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 OUStringLiteral s_sMediaType(u"MediaType"); + 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 +{ + 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 000000000..6ca7c36d2 --- /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/compbase3.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::WeakAggComponentImplHelper3 < 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 000000000..bc9ae43e4 --- /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(const 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 000000000..9acff0a7f --- /dev/null +++ b/comphelper/source/container/interfacecontainer2.cxx @@ -0,0 +1,421 @@ +/* -*- 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() ); + 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 000000000..c13ee7486 --- /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: */ diff --git a/comphelper/source/eventattachermgr/eventattachermgr.cxx b/comphelper/source/eventattachermgr/eventattachermgr.cxx new file mode 100644 index 000000000..1c9f1a23c --- /dev/null +++ b/comphelper/source/eventattachermgr/eventattachermgr.cxx @@ -0,0 +1,740 @@ +/* -*- 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 <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <osl/mutex.hxx> +#include <osl/diagnose.h> +#include <comphelper/eventattachermgr.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/beans/theIntrospection.hpp> +#include <com/sun/star/io/XObjectInputStream.hpp> +#include <com/sun/star/io/XPersistObject.hpp> +#include <com/sun/star/io/XObjectOutputStream.hpp> +#include <com/sun/star/io/XMarkableStream.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/reflection/theCoreReflection.hpp> +#include <com/sun/star/reflection/XIdlClass.hpp> +#include <com/sun/star/reflection/XIdlReflection.hpp> +#include <com/sun/star/reflection/XIdlMethod.hpp> +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/script/Converter.hpp> +#include <com/sun/star/script/XEventAttacher2.hpp> +#include <com/sun/star/script/XEventAttacherManager.hpp> +#include <com/sun/star/script/XScriptListener.hpp> +#include <cppuhelper/weak.hxx> +#include <comphelper/interfacecontainer3.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> + +#include <deque> +#include <algorithm> +#include <utility> + +using namespace com::sun::star::uno; +using namespace com::sun::star::io; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::script; +using namespace com::sun::star::reflection; +using namespace cppu; +using namespace osl; + + +namespace comphelper +{ + +namespace { + +struct AttachedObject_Impl +{ + Reference< XInterface > xTarget; + std::vector< Reference< XEventListener > > aAttachedListenerSeq; + Any aHelper; +}; + +struct AttacherIndex_Impl +{ + std::deque< ScriptEventDescriptor > aEventList; + std::deque< AttachedObject_Impl > aObjList; +}; + + +class ImplEventAttacherManager + : public WeakImplHelper< XEventAttacherManager, XPersistObject > +{ + friend class AttacherAllListener_Impl; + std::deque< AttacherIndex_Impl > aIndex; + Mutex aLock; + // Container for the ScriptListener + OInterfaceContainerHelper3<XScriptListener> aScriptListeners; + // Instance of EventAttacher + Reference< XEventAttacher2 > xAttacher; + Reference< XComponentContext > mxContext; + Reference< XIdlReflection > mxCoreReflection; + Reference< XTypeConverter > xConverter; + sal_Int16 nVersion; +public: + ImplEventAttacherManager( const Reference< XIntrospection > & rIntrospection, + const Reference< XComponentContext >& rContext ); + + // Methods of XEventAttacherManager + virtual void SAL_CALL registerScriptEvent(sal_Int32 Index, const ScriptEventDescriptor& ScriptEvent) override; + virtual void SAL_CALL registerScriptEvents(sal_Int32 Index, const Sequence< ScriptEventDescriptor >& ScriptEvents) override; + virtual void SAL_CALL revokeScriptEvent(sal_Int32 Index, const OUString& ListenerType, const OUString& EventMethod, const OUString& removeListenerParam) override; + virtual void SAL_CALL revokeScriptEvents(sal_Int32 Index) override; + virtual void SAL_CALL insertEntry(sal_Int32 Index) override; + virtual void SAL_CALL removeEntry(sal_Int32 Index) override; + virtual Sequence< ScriptEventDescriptor > SAL_CALL getScriptEvents(sal_Int32 Index) override; + virtual void SAL_CALL attach(sal_Int32 Index, const Reference< XInterface >& Object, const Any& Helper) override; + virtual void SAL_CALL detach(sal_Int32 nIndex, const Reference< XInterface >& xObject) override; + virtual void SAL_CALL addScriptListener(const Reference< XScriptListener >& aListener) override; + virtual void SAL_CALL removeScriptListener(const Reference< XScriptListener >& Listener) override; + + // Methods of XPersistObject + virtual OUString SAL_CALL getServiceName() override; + virtual void SAL_CALL write(const Reference< XObjectOutputStream >& OutStream) override; + virtual void SAL_CALL read(const Reference< XObjectInputStream >& InStream) override; + +private: + /// @throws Exception + Reference< XIdlReflection > getReflection(); + + /** checks if <arg>_nIndex</arg> is a valid index, throws an <type>IllegalArgumentException</type> if not + @param _nIndex + the index to check + @return + the iterator pointing to the position indicated by the index + */ + std::deque<AttacherIndex_Impl>::iterator implCheckIndex( sal_Int32 _nIndex ); +}; + + +// Implementation of an EventAttacher-subclass 'AllListeners', which +// only passes individual events of the general AllListeners. +class AttacherAllListener_Impl : public WeakImplHelper< XAllListener > +{ + rtl::Reference<ImplEventAttacherManager> mxManager; + OUString const aScriptType; + OUString const aScriptCode; + + /// @throws CannotConvertException + void convertToEventReturn( Any & rRet, const Type & rRetType ); +public: + AttacherAllListener_Impl( ImplEventAttacherManager* pManager_, OUString aScriptType_, + OUString aScriptCode_ ); + + // Methods of XAllListener + virtual void SAL_CALL firing(const AllEventObject& Event) override; + virtual Any SAL_CALL approveFiring(const AllEventObject& Event) override; + + // Methods of XEventListener + virtual void SAL_CALL disposing(const EventObject& Source) override; +}; + +} + +AttacherAllListener_Impl::AttacherAllListener_Impl +( + ImplEventAttacherManager* pManager_, + OUString aScriptType_, + OUString aScriptCode_ +) + : mxManager( pManager_ ) + , aScriptType(std::move( aScriptType_ )) + , aScriptCode(std::move( aScriptCode_ )) +{ +} + + +// Methods of XAllListener +void SAL_CALL AttacherAllListener_Impl::firing(const AllEventObject& Event) +{ + ScriptEvent aScriptEvent; + aScriptEvent.Source = static_cast<OWeakObject *>(mxManager.get()); // get correct XInterface + aScriptEvent.ListenerType = Event.ListenerType; + aScriptEvent.MethodName = Event.MethodName; + aScriptEvent.Arguments = Event.Arguments; + aScriptEvent.Helper = Event.Helper; + aScriptEvent.ScriptType = aScriptType; + aScriptEvent.ScriptCode = aScriptCode; + + // Iterate over all listeners and pass events. + mxManager->aScriptListeners.notifyEach( &XScriptListener::firing, aScriptEvent ); +} + + +// Convert to the standard event return +void AttacherAllListener_Impl::convertToEventReturn( Any & rRet, const Type & rRetType ) +{ + // no return value? Set to the specified values + if( rRet.getValueType().getTypeClass() == TypeClass_VOID ) + { + switch( rRetType.getTypeClass() ) + { + case TypeClass_INTERFACE: + { + rRet <<= Reference< XInterface >(); + } + break; + + case TypeClass_BOOLEAN: + rRet <<= true; + break; + + case TypeClass_STRING: + rRet <<= OUString(); + break; + + case TypeClass_FLOAT: rRet <<= float(0); break; + case TypeClass_DOUBLE: rRet <<= 0.0; break; + case TypeClass_BYTE: rRet <<= sal_uInt8(0); break; + case TypeClass_SHORT: rRet <<= sal_Int16( 0 ); break; + case TypeClass_LONG: rRet <<= sal_Int32( 0 ); break; + case TypeClass_UNSIGNED_SHORT: rRet <<= sal_uInt16( 0 ); break; + case TypeClass_UNSIGNED_LONG: rRet <<= sal_uInt32( 0 ); break; + + default: + OSL_ASSERT(false); + break; + } + } + else if( !rRet.getValueType().equals( rRetType ) ) + { + if( !mxManager->xConverter.is() ) + throw CannotConvertException(); + rRet = mxManager->xConverter->convertTo( rRet, rRetType ); + } +} + +// Methods of XAllListener +Any SAL_CALL AttacherAllListener_Impl::approveFiring( const AllEventObject& Event ) +{ + ScriptEvent aScriptEvent; + aScriptEvent.Source = static_cast<OWeakObject *>(mxManager.get()); // get correct XInterface + aScriptEvent.ListenerType = Event.ListenerType; + aScriptEvent.MethodName = Event.MethodName; + aScriptEvent.Arguments = Event.Arguments; + aScriptEvent.Helper = Event.Helper; + aScriptEvent.ScriptType = aScriptType; + aScriptEvent.ScriptCode = aScriptCode; + + Any aRet; + // Iterate over all listeners and pass events. + OInterfaceIteratorHelper3 aIt( mxManager->aScriptListeners ); + while( aIt.hasMoreElements() ) + { + aRet = aIt.next()->approveFiring( aScriptEvent ); + try + { + Reference< XIdlClass > xListenerType = mxManager->getReflection()-> + forName( Event.ListenerType.getTypeName() ); + Reference< XIdlMethod > xMeth = xListenerType->getMethod( Event.MethodName ); + if( xMeth.is() ) + { + Reference< XIdlClass > xRetType = xMeth->getReturnType(); + Type aRetType(xRetType->getTypeClass(), xRetType->getName()); + convertToEventReturn( aRet, aRetType ); + } + + switch( aRet.getValueType().getTypeClass() ) + { + case TypeClass_INTERFACE: + { + // Interface not null, return + Reference< XInterface > x; + aRet >>= x; + if( x.is() ) + return aRet; + } + break; + + case TypeClass_BOOLEAN: + // FALSE -> Return + if( !(*o3tl::forceAccess<bool>(aRet)) ) + return aRet; + break; + + case TypeClass_STRING: + // none empty string -> return + if( !o3tl::forceAccess<OUString>(aRet)->isEmpty() ) + return aRet; + break; + + // none zero number -> return + case TypeClass_FLOAT: if( *o3tl::forceAccess<float>(aRet) ) return aRet; break; + case TypeClass_DOUBLE: if( *o3tl::forceAccess<double>(aRet) ) return aRet; break; + case TypeClass_BYTE: if( *o3tl::forceAccess<sal_Int8>(aRet) ) return aRet; break; + case TypeClass_SHORT: if( *o3tl::forceAccess<sal_Int16>(aRet) ) return aRet; break; + case TypeClass_LONG: if( *o3tl::forceAccess<sal_Int32>(aRet) ) return aRet; break; + case TypeClass_UNSIGNED_SHORT: if( *o3tl::forceAccess<sal_uInt16>(aRet) ) return aRet; break; + case TypeClass_UNSIGNED_LONG: if( *o3tl::forceAccess<sal_uInt32>(aRet) ) return aRet; break; + + default: + OSL_ASSERT(false); + break; + } + } + catch (const CannotConvertException&) + { + // silent ignore conversions errors from a script call + Reference< XIdlClass > xListenerType = mxManager->getReflection()-> + forName( Event.ListenerType.getTypeName() ); + Reference< XIdlMethod > xMeth = xListenerType->getMethod( Event.MethodName ); + if( xMeth.is() ) + { + Reference< XIdlClass > xRetType = xMeth->getReturnType(); + Type aRetType(xRetType->getTypeClass(), xRetType->getName()); + aRet.clear(); + try + { + convertToEventReturn( aRet, aRetType ); + } + catch (const CannotConvertException& e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( + "wrapped CannotConvertException " + e.Message, + css::uno::Reference<css::uno::XInterface>(), anyEx); + } + } + } + } + return aRet; +} + +// Methods of XEventListener +void SAL_CALL AttacherAllListener_Impl::disposing(const EventObject& ) +{ + // It is up to the container to release the object +} + +// Constructor method for EventAttacherManager +Reference< XEventAttacherManager > createEventAttacherManager( const Reference< XComponentContext > & rxContext ) +{ + Reference< XIntrospection > xIntrospection = theIntrospection::get( rxContext ); + return new ImplEventAttacherManager( xIntrospection, rxContext ); +} + + +ImplEventAttacherManager::ImplEventAttacherManager( const Reference< XIntrospection > & rIntrospection, + const Reference< XComponentContext >& rContext ) + : aScriptListeners( aLock ) + , mxContext( rContext ) + , nVersion(0) +{ + if ( rContext.is() ) + { + Reference< XInterface > xIFace( rContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.script.EventAttacher", rContext) ); + if ( xIFace.is() ) + { + xAttacher.set( xIFace, UNO_QUERY ); + } + xConverter = Converter::create(rContext); + } + + Reference< XInitialization > xInit( xAttacher, UNO_QUERY ); + if( xInit.is() ) + { + xInit->initialize({ Any(rIntrospection) }); + } +} + +Reference< XIdlReflection > ImplEventAttacherManager::getReflection() +{ + Guard< Mutex > aGuard( aLock ); + // Do we already have a service? If not, create one. + if( !mxCoreReflection.is() ) + { + mxCoreReflection = theCoreReflection::get(mxContext); + } + return mxCoreReflection; +} + + +std::deque< AttacherIndex_Impl >::iterator ImplEventAttacherManager::implCheckIndex( sal_Int32 _nIndex ) +{ + if ( (_nIndex < 0) || (o3tl::make_unsigned(_nIndex) >= aIndex.size()) ) + throw IllegalArgumentException("wrong index", static_cast<cppu::OWeakObject*>(this), 1); + + std::deque<AttacherIndex_Impl>::iterator aIt = aIndex.begin() + _nIndex; + return aIt; +} + +// Methods of XEventAttacherManager +void SAL_CALL ImplEventAttacherManager::registerScriptEvent +( + sal_Int32 nIndex, + const ScriptEventDescriptor& ScriptEvent +) +{ + Guard< Mutex > aGuard( aLock ); + + // Examine the index and apply the array + std::deque<AttacherIndex_Impl>::iterator aIt = implCheckIndex( nIndex ); + + ScriptEventDescriptor aEvt = ScriptEvent; + sal_Int32 nLastDot = aEvt.ListenerType.lastIndexOf('.'); + if (nLastDot != -1) + aEvt.ListenerType = aEvt.ListenerType.copy(nLastDot+1); + aIt->aEventList.push_back( aEvt ); + + // register new Event + for( auto& rObj : aIt->aObjList ) + { + Reference< XAllListener > xAll = + new AttacherAllListener_Impl( this, ScriptEvent.ScriptType, ScriptEvent.ScriptCode ); + try + { + rObj.aAttachedListenerSeq.push_back( xAttacher->attachSingleEventListener( rObj.xTarget, xAll, + rObj.aHelper, ScriptEvent.ListenerType, + ScriptEvent.AddListenerParam, ScriptEvent.EventMethod ) ); + } + catch( Exception& ) + { + } + } +} + + +void SAL_CALL ImplEventAttacherManager::registerScriptEvents +( + sal_Int32 nIndex, + const Sequence< ScriptEventDescriptor >& ScriptEvents +) +{ + Guard< Mutex > aGuard( aLock ); + + // Examine the index and apply the array + std::deque< AttachedObject_Impl > aList = implCheckIndex( nIndex )->aObjList; + for( const auto& rObj : aList ) + detach( nIndex, rObj.xTarget ); + + const ScriptEventDescriptor* pArray = ScriptEvents.getConstArray(); + sal_Int32 nLen = ScriptEvents.getLength(); + for( sal_Int32 i = 0 ; i < nLen ; i++ ) + registerScriptEvent( nIndex, pArray[ i ] ); + + for( const auto& rObj : aList ) + attach( nIndex, rObj.xTarget, rObj.aHelper ); +} + + +void SAL_CALL ImplEventAttacherManager::revokeScriptEvent +( + sal_Int32 nIndex, + const OUString& ListenerType, + const OUString& EventMethod, + const OUString& ToRemoveListenerParam +) +{ + Guard< Mutex > aGuard( aLock ); + + std::deque<AttacherIndex_Impl>::iterator aIt = implCheckIndex( nIndex ); + + std::deque< AttachedObject_Impl > aList = aIt->aObjList; + for( const auto& rObj : aList ) + detach( nIndex, rObj.xTarget ); + + std::u16string_view aLstType = ListenerType; + size_t nLastDot = aLstType.rfind('.'); + if (nLastDot != std::u16string_view::npos) + aLstType = aLstType.substr(nLastDot+1); + + auto aEvtIt = std::find_if(aIt->aEventList.begin(), aIt->aEventList.end(), + [&aLstType, &EventMethod, &ToRemoveListenerParam](const ScriptEventDescriptor& rEvent) { + return aLstType == rEvent.ListenerType + && EventMethod == rEvent.EventMethod + && ToRemoveListenerParam == rEvent.AddListenerParam; + }); + if (aEvtIt != aIt->aEventList.end()) + aIt->aEventList.erase( aEvtIt ); + + for( const auto& rObj : aList ) + attach( nIndex, rObj.xTarget, rObj.aHelper ); +} + + +void SAL_CALL ImplEventAttacherManager::revokeScriptEvents(sal_Int32 nIndex ) +{ + Guard< Mutex > aGuard( aLock ); + std::deque<AttacherIndex_Impl>::iterator aIt = implCheckIndex( nIndex ); + + std::deque< AttachedObject_Impl > aList = aIt->aObjList; + for( const auto& rObj : aList ) + detach( nIndex, rObj.xTarget ); + aIt->aEventList.clear(); + for( const auto& rObj : aList ) + attach( nIndex, rObj.xTarget, rObj.aHelper ); +} + + +void SAL_CALL ImplEventAttacherManager::insertEntry(sal_Int32 nIndex) +{ + Guard< Mutex > aGuard( aLock ); + if( nIndex < 0 ) + throw IllegalArgumentException("negative index", static_cast<cppu::OWeakObject*>(this), 1); + + if ( o3tl::make_unsigned(nIndex) >= aIndex.size() ) + aIndex.resize(nIndex+1); + + AttacherIndex_Impl aTmp; + aIndex.insert( aIndex.begin() + nIndex, aTmp ); +} + + +void SAL_CALL ImplEventAttacherManager::removeEntry(sal_Int32 nIndex) +{ + Guard< Mutex > aGuard( aLock ); + std::deque<AttacherIndex_Impl>::iterator aIt = implCheckIndex( nIndex ); + + std::deque< AttachedObject_Impl > aList = aIt->aObjList; + for( const auto& rObj : aList ) + detach( nIndex, rObj.xTarget ); + + aIndex.erase( aIt ); +} + + +Sequence< ScriptEventDescriptor > SAL_CALL ImplEventAttacherManager::getScriptEvents(sal_Int32 nIndex) +{ + Guard< Mutex > aGuard( aLock ); + std::deque<AttacherIndex_Impl>::iterator aIt = implCheckIndex( nIndex ); + return comphelper::containerToSequence(aIt->aEventList); +} + + +void SAL_CALL ImplEventAttacherManager::attach(sal_Int32 nIndex, const Reference< XInterface >& xObject, const Any & Helper) +{ + Guard< Mutex > aGuard( aLock ); + if( nIndex < 0 || !xObject.is() ) + throw IllegalArgumentException("negative index, or null object", static_cast<cppu::OWeakObject*>(this), -1); + + if( o3tl::make_unsigned(nIndex) >= aIndex.size() ) + { + // read older files + if( nVersion != 1 ) + throw IllegalArgumentException(); + insertEntry( nIndex ); + attach( nIndex, xObject, Helper ); + return; + } + + std::deque< AttacherIndex_Impl >::iterator aCurrentPosition = aIndex.begin() + nIndex; + + AttachedObject_Impl aTmp; + aTmp.xTarget = xObject; + aTmp.aHelper = Helper; + aCurrentPosition->aObjList.push_back( aTmp ); + + AttachedObject_Impl & rCurObj = aCurrentPosition->aObjList.back(); + rCurObj.aAttachedListenerSeq = std::vector< Reference< XEventListener > >( aCurrentPosition->aEventList.size() ); + + if (aCurrentPosition->aEventList.empty()) + return; + + Sequence<css::script::EventListener> aEvents(aCurrentPosition->aEventList.size()); + css::script::EventListener* p = aEvents.getArray(); + size_t i = 0; + for (const auto& rEvent : aCurrentPosition->aEventList) + { + css::script::EventListener aListener; + aListener.AllListener = + new AttacherAllListener_Impl(this, rEvent.ScriptType, rEvent.ScriptCode); + aListener.Helper = rCurObj.aHelper; + aListener.ListenerType = rEvent.ListenerType; + aListener.EventMethod = rEvent.EventMethod; + aListener.AddListenerParam = rEvent.AddListenerParam; + p[i++] = aListener; + } + + try + { + rCurObj.aAttachedListenerSeq = comphelper::sequenceToContainer<std::vector<Reference< XEventListener >>>( + xAttacher->attachMultipleEventListeners(rCurObj.xTarget, aEvents)); + } + catch (const Exception&) + { + // Fail gracefully. + } +} + + +void SAL_CALL ImplEventAttacherManager::detach(sal_Int32 nIndex, const Reference< XInterface >& xObject) +{ + Guard< Mutex > aGuard( aLock ); + //return; + if( nIndex < 0 || o3tl::make_unsigned(nIndex) >= aIndex.size() || !xObject.is() ) + throw IllegalArgumentException("bad index or null object", static_cast<cppu::OWeakObject*>(this), 1); + + std::deque< AttacherIndex_Impl >::iterator aCurrentPosition = aIndex.begin() + nIndex; + auto aObjIt = std::find_if(aCurrentPosition->aObjList.begin(), aCurrentPosition->aObjList.end(), + [&xObject](const AttachedObject_Impl& rObj) { return rObj.xTarget == xObject; }); + if (aObjIt == aCurrentPosition->aObjList.end()) + return; + + sal_Int32 i = 0; + for( const auto& rEvt : aCurrentPosition->aEventList ) + { + if( aObjIt->aAttachedListenerSeq[i].is() ) + { + try + { + xAttacher->removeListener( aObjIt->xTarget, rEvt.ListenerType, + rEvt.AddListenerParam, aObjIt->aAttachedListenerSeq[i] ); + } + catch( Exception& ) + { + } + } + ++i; + } + aCurrentPosition->aObjList.erase( aObjIt ); +} + +void SAL_CALL ImplEventAttacherManager::addScriptListener(const Reference< XScriptListener >& aListener) +{ + Guard< Mutex > aGuard( aLock ); + aScriptListeners.addInterface( aListener ); +} + +void SAL_CALL ImplEventAttacherManager::removeScriptListener(const Reference< XScriptListener >& aListener) +{ + Guard< Mutex > aGuard( aLock ); + aScriptListeners.removeInterface( aListener ); +} + + +// Methods of XPersistObject +OUString SAL_CALL ImplEventAttacherManager::getServiceName() +{ + return "com.sun.star.uno.script.EventAttacherManager"; +} + +void SAL_CALL ImplEventAttacherManager::write(const Reference< XObjectOutputStream >& OutStream) +{ + Guard< Mutex > aGuard( aLock ); + // Don't run without XMarkableStream + Reference< XMarkableStream > xMarkStream( OutStream, UNO_QUERY ); + if( !xMarkStream.is() ) + return; + + // Write out the version + OutStream->writeShort( 2 ); + + // Remember position for length + sal_Int32 nObjLenMark = xMarkStream->createMark(); + OutStream->writeLong( 0 ); + + OutStream->writeLong( aIndex.size() ); + + // Write out sequences + for( const auto& rIx : aIndex ) + { + OutStream->writeLong( rIx.aEventList.size() ); + for( const auto& rDesc : rIx.aEventList ) + { + OutStream->writeUTF( rDesc.ListenerType ); + OutStream->writeUTF( rDesc.EventMethod ); + OutStream->writeUTF( rDesc.AddListenerParam ); + OutStream->writeUTF( rDesc.ScriptType ); + OutStream->writeUTF( rDesc.ScriptCode ); + } + } + + // The length is now known + sal_Int32 nObjLen = xMarkStream->offsetToMark( nObjLenMark ) -4; + xMarkStream->jumpToMark( nObjLenMark ); + OutStream->writeLong( nObjLen ); + xMarkStream->jumpToFurthest(); + xMarkStream->deleteMark( nObjLenMark ); +} + +void SAL_CALL ImplEventAttacherManager::read(const Reference< XObjectInputStream >& InStream) +{ + Guard< Mutex > aGuard( aLock ); + // Don't run without XMarkableStream + Reference< XMarkableStream > xMarkStream( InStream, UNO_QUERY ); + if( !xMarkStream.is() ) + return; + + // Read in the version + nVersion = InStream->readShort(); + + // At first there's the data according to version 1 -- + // this part needs to be kept in later versions. + sal_Int32 nLen = InStream->readLong(); + + // Position for comparative purposes + sal_Int32 nObjLenMark = xMarkStream->createMark(); + + // Number of read sequences + sal_Int32 nItemCount = InStream->readLong(); + + for( sal_Int32 i = 0 ; i < nItemCount ; i++ ) + { + insertEntry( i ); + // Read the length of the sequence + sal_Int32 nSeqLen = InStream->readLong(); + + // Display the sequences and read the descriptions + Sequence< ScriptEventDescriptor > aSEDSeq( nSeqLen ); + ScriptEventDescriptor* pArray = aSEDSeq.getArray(); + for( sal_Int32 j = 0 ; j < nSeqLen ; j++ ) + { + ScriptEventDescriptor& rDesc = pArray[ j ]; + rDesc.ListenerType = InStream->readUTF(); + rDesc.EventMethod = InStream->readUTF(); + rDesc.AddListenerParam = InStream->readUTF(); + rDesc.ScriptType = InStream->readUTF(); + rDesc.ScriptCode = InStream->readUTF(); + } + registerScriptEvents( i, aSEDSeq ); + } + + // Have we read the specified length? + sal_Int32 nRealLen = xMarkStream->offsetToMark( nObjLenMark ); + if( nRealLen != nLen ) + { + // Only if the StreamVersion is > 1 and the date still follows, can + // this be true. Otherwise, something is completely gone. + if( nRealLen > nLen || nVersion == 1 ) + { + OSL_FAIL( "ImplEventAttacherManager::read(): Fatal Error, wrong object length" ); + } + else + { // TODO: Examine if caching the dates would be useful + // But for now, it's easier to skip it. + sal_Int32 nSkipCount = nLen - nRealLen; + InStream->skipBytes( nSkipCount ); + } + } + xMarkStream->jumpToFurthest(); + xMarkStream->deleteMark( nObjLenMark ); +} + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/AccessibleImplementationHelper.cxx b/comphelper/source/misc/AccessibleImplementationHelper.cxx new file mode 100644 index 000000000..a45b6e5f3 --- /dev/null +++ b/comphelper/source/misc/AccessibleImplementationHelper.cxx @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/AccessibleImplementationHelper.hxx> + +#include <com/sun/star/awt/KeyStroke.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <rtl/ustrbuf.hxx> + +using namespace css::awt; +using namespace css::uno; + +namespace comphelper +{ +OUString GetkeyBindingStrByXkeyBinding(const Sequence<KeyStroke>& keySet) +{ + OUStringBuffer buf; + for (const auto& k : keySet) + { + buf.append('\n'); + buf.append(k.KeyChar); + } + return buf.makeStringAndClear(); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/comphelper/source/misc/DirectoryHelper.cxx b/comphelper/source/misc/DirectoryHelper.cxx new file mode 100644 index 000000000..badfe9b62 --- /dev/null +++ b/comphelper/source/misc/DirectoryHelper.cxx @@ -0,0 +1,213 @@ +/* -*- 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/. + */ + +#include <comphelper/DirectoryHelper.hxx> + +#include <sal/config.h> +#include <osl/file.hxx> +#include <rtl/uri.hxx> + +#include <memory> + +namespace comphelper +{ +typedef std::shared_ptr<osl::File> FileSharedPtr; + +std::u16string_view DirectoryHelper::splitAtLastToken(std::u16string_view rSrc, sal_Unicode aToken, + OUString& rRight) +{ + const size_t nIndex(rSrc.rfind(aToken)); + std::u16string_view aRetval; + + if (std::u16string_view::npos == nIndex) + { + aRetval = rSrc; + rRight.clear(); + } + else if (nIndex > 0) + { + aRetval = rSrc.substr(0, nIndex); + + if (rSrc.size() > nIndex + 1) + { + rRight = rSrc.substr(nIndex + 1); + } + } + + return aRetval; +} + +bool DirectoryHelper::fileExists(const OUString& rBaseURL) +{ + if (!rBaseURL.isEmpty()) + { + FileSharedPtr aBaseFile = std::make_shared<osl::File>(rBaseURL); + + return (osl::File::E_None == aBaseFile->open(osl_File_OpenFlag_Read)); + } + + return false; +} + +bool DirectoryHelper::dirExists(const OUString& rDirURL) +{ + if (!rDirURL.isEmpty()) + { + osl::Directory aDirectory(rDirURL); + + return (osl::FileBase::E_None == aDirectory.open()); + } + + return false; +} + +void DirectoryHelper::scanDirsAndFiles(const OUString& rDirURL, std::set<OUString>& rDirs, + std::set<std::pair<OUString, OUString>>& rFiles) +{ + if (rDirURL.isEmpty()) + return; + + osl::Directory aDirectory(rDirURL); + + if (osl::FileBase::E_None != aDirectory.open()) + return; + + auto lcl_encodeUriSegment = [](OUString const& rPath) { + return rtl::Uri::encode(rPath, rtl_UriCharClassUricNoSlash, rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8); + }; + + osl::DirectoryItem aDirectoryItem; + + while (osl::FileBase::E_None == aDirectory.getNextItem(aDirectoryItem)) + { + osl::FileStatus aFileStatus(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName); + + if (osl::FileBase::E_None == aDirectoryItem.getFileStatus(aFileStatus)) + { + if (aFileStatus.isDirectory()) + { + const OUString aFileName(aFileStatus.getFileName()); + + if (!aFileName.isEmpty()) + { + rDirs.insert(lcl_encodeUriSegment(aFileName)); + } + } + else if (aFileStatus.isRegular()) + { + OUString aFileName(aFileStatus.getFileName()); + OUString aExtension; + aFileName = splitAtLastToken(aFileName, '.', aExtension); + + if (!aFileName.isEmpty()) + { + rFiles.insert(std::pair<OUString, OUString>(lcl_encodeUriSegment(aFileName), + lcl_encodeUriSegment(aExtension))); + } + } + } + } +} + +bool DirectoryHelper::deleteDirRecursively(const OUString& rDirURL) +{ + std::set<OUString> aDirs; + std::set<std::pair<OUString, OUString>> aFiles; + bool bError(false); + + scanDirsAndFiles(rDirURL, aDirs, aFiles); + + for (const auto& dir : aDirs) + { + const OUString aNewDirURL(rDirURL + "/" + dir); + + bError |= deleteDirRecursively(aNewDirURL); + } + + for (const auto& file : aFiles) + { + OUString aNewFileURL(rDirURL + "/" + file.first); + + if (!file.second.isEmpty()) + { + aNewFileURL += "." + file.second; + } + bError |= (osl::FileBase::E_None != osl::File::remove(aNewFileURL)); + } + + bError |= (osl::FileBase::E_None != osl::Directory::remove(rDirURL)); + + return bError; +} + +// both exist, move content +bool DirectoryHelper::moveDirContent(const OUString& rSourceDirURL, + std::u16string_view rTargetDirURL, + const std::set<OUString>& rExcludeList) +{ + std::set<OUString> aDirs; + std::set<std::pair<OUString, OUString>> aFiles; + bool bError(false); + + scanDirsAndFiles(rSourceDirURL, aDirs, aFiles); + + for (const auto& dir : aDirs) + { + const bool bExcluded(!rExcludeList.empty() && rExcludeList.find(dir) != rExcludeList.end()); + + if (!bExcluded) + { + const OUString aNewSourceDirURL(rSourceDirURL + "/" + dir); + + if (dirExists(aNewSourceDirURL)) + { + const OUString aNewTargetDirURL(OUString::Concat(rTargetDirURL) + "/" + dir); + + if (dirExists(aNewTargetDirURL)) + { + deleteDirRecursively(aNewTargetDirURL); + } + + bError |= (osl::FileBase::E_None + != osl::File::move(aNewSourceDirURL, aNewTargetDirURL)); + } + } + } + + for (const auto& file : aFiles) + { + OUString aSourceFileURL(rSourceDirURL + "/" + file.first); + + if (!file.second.isEmpty()) + { + aSourceFileURL += "." + file.second; + } + + if (fileExists(aSourceFileURL)) + { + OUString aTargetFileURL(OUString::Concat(rTargetDirURL) + "/" + file.first); + + if (!file.second.isEmpty()) + { + aTargetFileURL += "." + file.second; + } + + if (fileExists(aTargetFileURL)) + { + osl::File::remove(aTargetFileURL); + } + + bError |= (osl::FileBase::E_None != osl::File::move(aSourceFileURL, aTargetFileURL)); + } + } + + return bError; +} +} diff --git a/comphelper/source/misc/SelectionMultiplex.cxx b/comphelper/source/misc/SelectionMultiplex.cxx new file mode 100644 index 000000000..438eaa80d --- /dev/null +++ b/comphelper/source/misc/SelectionMultiplex.cxx @@ -0,0 +1,111 @@ +/* -*- 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/SelectionMultiplex.hxx> +#include <com/sun/star/view/XSelectionSupplier.hpp> + +namespace comphelper +{ + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::view; + +OSelectionChangeListener::~OSelectionChangeListener() +{ +} + + +void OSelectionChangeListener::_disposing(const EventObject&) +{ + // nothing to do here +} + + +OSelectionChangeMultiplexer::OSelectionChangeMultiplexer(OSelectionChangeListener* _pListener, const Reference< XSelectionSupplier>& _rxSet) + :m_xSet(_rxSet) + ,m_pListener(_pListener) + ,m_nLockCount(0) +{ + osl_atomic_increment(&m_refCount); + { + Reference< XSelectionChangeListener> xPreventDelete(this); + m_xSet->addSelectionChangeListener(xPreventDelete); + } + osl_atomic_decrement(&m_refCount); +} + + +OSelectionChangeMultiplexer::~OSelectionChangeMultiplexer() +{ +} + + +void OSelectionChangeMultiplexer::lock() +{ + ++m_nLockCount; +} + + +void OSelectionChangeMultiplexer::unlock() +{ + --m_nLockCount; +} + + +// XEventListener + +void SAL_CALL OSelectionChangeMultiplexer::disposing( const EventObject& _rSource) +{ + if (m_pListener) + { + // tell the listener + if (!locked()) + m_pListener->_disposing(_rSource); + } + + m_pListener = nullptr; + + m_xSet = nullptr; +} + +// XSelectionChangeListener + +void SAL_CALL OSelectionChangeMultiplexer::selectionChanged( const EventObject& _rEvent ) +{ + if (m_pListener && !locked()) + m_pListener->_selectionChanged(_rEvent); +} + +void OSelectionChangeMultiplexer::dispose() +{ + osl_atomic_increment(&m_refCount); + { + Reference< XSelectionChangeListener> xPreventDelete(this); + if(m_xSet.is()) + m_xSet->removeSelectionChangeListener(xPreventDelete); + } + osl_atomic_decrement(&m_refCount); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/accessiblecomponenthelper.cxx b/comphelper/source/misc/accessiblecomponenthelper.cxx new file mode 100644 index 000000000..2962bbac9 --- /dev/null +++ b/comphelper/source/misc/accessiblecomponenthelper.cxx @@ -0,0 +1,177 @@ +/* -*- 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/accessiblecomponenthelper.hxx> + + +namespace comphelper +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::accessibility; + + OCommonAccessibleComponent::OCommonAccessibleComponent( ) + { + } + + + OCommonAccessibleComponent::~OCommonAccessibleComponent( ) + { + } + + + bool OCommonAccessibleComponent::containsPoint( const awt::Point& _rPoint ) + { + OExternalLockGuard aGuard( this ); + awt::Rectangle aBounds( implGetBounds() ); + return ( _rPoint.X >= 0 ) + && ( _rPoint.Y >= 0 ) + && ( _rPoint.X < aBounds.Width ) + && ( _rPoint.Y < aBounds.Height ); + } + + + awt::Point OCommonAccessibleComponent::getLocation( ) + { + OExternalLockGuard aGuard( this ); + awt::Rectangle aBounds( implGetBounds() ); + return awt::Point( aBounds.X, aBounds.Y ); + } + + + awt::Point OCommonAccessibleComponent::getLocationOnScreen( ) + { + OExternalLockGuard aGuard( this ); + + awt::Point aScreenLoc( 0, 0 ); + + Reference< XAccessibleComponent > xParentComponent( implGetParentContext(), UNO_QUERY ); + OSL_ENSURE( xParentComponent.is(), "OCommonAccessibleComponent::getLocationOnScreen: no parent component!" ); + if ( xParentComponent.is() ) + { + awt::Point aParentScreenLoc( xParentComponent->getLocationOnScreen() ); + awt::Point aOwnRelativeLoc( getLocation() ); + aScreenLoc.X = aParentScreenLoc.X + aOwnRelativeLoc.X; + aScreenLoc.Y = aParentScreenLoc.Y + aOwnRelativeLoc.Y; + } + + return aScreenLoc; + } + + + awt::Size OCommonAccessibleComponent::getSize( ) + { + OExternalLockGuard aGuard( this ); + awt::Rectangle aBounds( implGetBounds() ); + return awt::Size( aBounds.Width, aBounds.Height ); + } + + + awt::Rectangle OCommonAccessibleComponent::getBounds( ) + { + OExternalLockGuard aGuard( this ); + return implGetBounds(); + } + + OAccessibleComponentHelper::OAccessibleComponentHelper( ) + { + } + + + IMPLEMENT_FORWARD_XINTERFACE2( OAccessibleComponentHelper, OCommonAccessibleComponent, OAccessibleComponentHelper_Base ) + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OAccessibleComponentHelper, OCommonAccessibleComponent, OAccessibleComponentHelper_Base ) + // (order matters: the first is the class name, the second is the class doing the ref counting) + + + sal_Bool SAL_CALL OAccessibleComponentHelper::containsPoint( const awt::Point& _rPoint ) + { + return OCommonAccessibleComponent::containsPoint( _rPoint ); + } + + + awt::Point SAL_CALL OAccessibleComponentHelper::getLocation( ) + { + return OCommonAccessibleComponent::getLocation( ); + } + + + awt::Point SAL_CALL OAccessibleComponentHelper::getLocationOnScreen( ) + { + return OCommonAccessibleComponent::getLocationOnScreen( ); + } + + + awt::Size SAL_CALL OAccessibleComponentHelper::getSize( ) + { + return OCommonAccessibleComponent::getSize( ); + } + + + awt::Rectangle SAL_CALL OAccessibleComponentHelper::getBounds( ) + { + return OCommonAccessibleComponent::getBounds( ); + } + + OAccessibleExtendedComponentHelper::OAccessibleExtendedComponentHelper( ) + { + } + + + IMPLEMENT_FORWARD_XINTERFACE2( OAccessibleExtendedComponentHelper, OCommonAccessibleComponent, OAccessibleExtendedComponentHelper_Base ) + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OAccessibleExtendedComponentHelper, OCommonAccessibleComponent, OAccessibleExtendedComponentHelper_Base ) + // (order matters: the first is the class name, the second is the class doing the ref counting) + + + sal_Bool SAL_CALL OAccessibleExtendedComponentHelper::containsPoint( const awt::Point& _rPoint ) + { + return OCommonAccessibleComponent::containsPoint( _rPoint ); + } + + + awt::Point SAL_CALL OAccessibleExtendedComponentHelper::getLocation( ) + { + return OCommonAccessibleComponent::getLocation( ); + } + + + awt::Point SAL_CALL OAccessibleExtendedComponentHelper::getLocationOnScreen( ) + { + return OCommonAccessibleComponent::getLocationOnScreen( ); + } + + + awt::Size SAL_CALL OAccessibleExtendedComponentHelper::getSize( ) + { + return OCommonAccessibleComponent::getSize( ); + } + + + awt::Rectangle SAL_CALL OAccessibleExtendedComponentHelper::getBounds( ) + { + return OCommonAccessibleComponent::getBounds( ); + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/accessiblecontexthelper.cxx b/comphelper/source/misc/accessiblecontexthelper.cxx new file mode 100644 index 000000000..ad2c63b1a --- /dev/null +++ b/comphelper/source/misc/accessiblecontexthelper.cxx @@ -0,0 +1,248 @@ +/* -*- 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/accessiblecontexthelper.hxx> +#include <osl/diagnose.h> +#include <cppuhelper/weakref.hxx> +#include <com/sun/star/accessibility/IllegalAccessibleComponentStateException.hpp> +#include <comphelper/accessibleeventnotifier.hxx> +#include <comphelper/solarmutex.hxx> + + +namespace comphelper +{ + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::accessibility; + + OAccessibleContextHelper::OAccessibleContextHelper( ) + :OAccessibleContextHelper_Base( GetMutex() ) + ,m_nClientId( 0 ) + { + } + + + OAccessibleContextHelper::~OAccessibleContextHelper( ) + { + // this ensures that the lock, which may be already destroyed as part of the derivee, + // is not used anymore + + ensureDisposed(); + } + + + void SAL_CALL OAccessibleContextHelper::disposing() + { + // rhbz#1001768: de facto this class is locked by SolarMutex; + // do not lock m_Mutex because it may cause deadlock + osl::Guard<SolarMutex> aGuard(SolarMutex::get()); + + if ( m_nClientId ) + { + AccessibleEventNotifier::revokeClientNotifyDisposing( m_nClientId, *this ); + m_nClientId=0; + } + } + + + void SAL_CALL OAccessibleContextHelper::addAccessibleEventListener( const Reference< XAccessibleEventListener >& _rxListener ) + { + osl::Guard<SolarMutex> aGuard(SolarMutex::get()); + // don't use the OContextEntryGuard - it will throw an exception if we're not alive + // anymore, while the most recent specification for XComponent states that we should + // silently ignore the call in such a situation + if ( !isAlive() ) + { + if ( _rxListener.is() ) + _rxListener->disposing( EventObject( *this ) ); + return; + } + + if ( _rxListener.is() ) + { + if ( !m_nClientId ) + m_nClientId = AccessibleEventNotifier::registerClient( ); + + AccessibleEventNotifier::addEventListener( m_nClientId, _rxListener ); + } + } + + + void SAL_CALL OAccessibleContextHelper::removeAccessibleEventListener( const Reference< XAccessibleEventListener >& _rxListener ) + { + osl::Guard<SolarMutex> aGuard(SolarMutex::get()); + // don't use the OContextEntryGuard - it will throw an exception if we're not alive + // anymore, while the most recent specification for XComponent states that we should + // silently ignore the call in such a situation + if ( !isAlive() ) + return; + + if ( !(_rxListener.is() && m_nClientId) ) + return; + + sal_Int32 nListenerCount = AccessibleEventNotifier::removeEventListener( m_nClientId, _rxListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + AccessibleEventNotifier::revokeClient( m_nClientId ); + m_nClientId = 0; + } + } + + + void OAccessibleContextHelper::NotifyAccessibleEvent( const sal_Int16 _nEventId, + const Any& _rOldValue, const Any& _rNewValue ) + { + if ( !m_nClientId ) + // if we don't have a client id for the notifier, then we don't have listeners, then + // we don't need to notify anything + return; + + // build an event object + AccessibleEventObject aEvent; + aEvent.Source = *this; + aEvent.EventId = _nEventId; + aEvent.OldValue = _rOldValue; + aEvent.NewValue = _rNewValue; + + // let the notifier handle this event + AccessibleEventNotifier::addEvent( m_nClientId, aEvent ); + } + + + bool OAccessibleContextHelper::isAlive() const + { + return !rBHelper.bDisposed && !rBHelper.bInDispose; + } + + + void OAccessibleContextHelper::ensureAlive() const + { + if( !isAlive() ) + throw DisposedException(); + } + + + void OAccessibleContextHelper::ensureDisposed( ) + { + if ( !rBHelper.bDisposed ) + { + OSL_ENSURE( 0 == m_refCount, "OAccessibleContextHelper::ensureDisposed: this method _has_ to be called from without your dtor only!" ); + acquire(); + dispose(); + } + } + + + void OAccessibleContextHelper::lateInit( const Reference< XAccessible >& _rxAccessible ) + { + m_aCreator = _rxAccessible; + } + + + Reference< XAccessible > OAccessibleContextHelper::getAccessibleCreator( ) const + { + return m_aCreator; + } + + + OUString SAL_CALL OAccessibleContextHelper::getAccessibleId( ) + { + return OUString(); + } + + + sal_Int32 SAL_CALL OAccessibleContextHelper::getAccessibleIndexInParent( ) + { + OExternalLockGuard aGuard( this ); + + // -1 for child not found/no parent (according to specification) + sal_Int32 nRet = -1; + + try + { + + Reference< XAccessibleContext > xParentContext( implGetParentContext() ); + + // iterate over parent's children and search for this object + if ( xParentContext.is() ) + { + // our own XAccessible for comparing with the children of our parent + Reference< XAccessible > xCreator( m_aCreator); + + OSL_ENSURE( xCreator.is(), "OAccessibleContextHelper::getAccessibleIndexInParent: invalid creator!" ); + // two ideas why this could be NULL: + // * nobody called our late ctor (init), so we never had a creator at all -> bad + // * the creator is already dead. In this case, we should have been disposed, and + // never survived the above OContextEntryGuard. + // in all other situations the creator should be non-NULL + + if ( xCreator.is() ) + { + sal_Int32 nChildCount = xParentContext->getAccessibleChildCount(); + for ( sal_Int32 nChild = 0; ( nChild < nChildCount ) && ( -1 == nRet ); ++nChild ) + { + Reference< XAccessible > xChild( xParentContext->getAccessibleChild( nChild ) ); + if ( xChild.get() == xCreator.get() ) + nRet = nChild; + } + } + } + } + catch( const Exception& ) + { + OSL_FAIL( "OAccessibleContextHelper::getAccessibleIndexInParent: caught an exception!" ); + } + + return nRet; + } + + + Locale SAL_CALL OAccessibleContextHelper::getLocale( ) + { + // simply ask the parent + Reference< XAccessible > xParent = getAccessibleParent(); + Reference< XAccessibleContext > xParentContext; + if ( xParent.is() ) + xParentContext = xParent->getAccessibleContext(); + + if ( !xParentContext.is() ) + throw IllegalAccessibleComponentStateException( OUString(), *this ); + + return xParentContext->getLocale(); + } + + + Reference< XAccessibleContext > OAccessibleContextHelper::implGetParentContext() + { + Reference< XAccessible > xParent = getAccessibleParent(); + Reference< XAccessibleContext > xParentContext; + if ( xParent.is() ) + xParentContext = xParent->getAccessibleContext(); + return xParentContext; + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/accessibleeventnotifier.cxx b/comphelper/source/misc/accessibleeventnotifier.cxx new file mode 100644 index 000000000..f8bd5a117 --- /dev/null +++ b/comphelper/source/misc/accessibleeventnotifier.cxx @@ -0,0 +1,274 @@ +/* -*- 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/accessibleeventnotifier.hxx> +#include <com/sun/star/accessibility/XAccessibleEventListener.hpp> +#include <comphelper/interfacecontainer4.hxx> + +#include <map> +#include <memory> +#include <limits> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::accessibility; +using namespace ::comphelper; + +namespace { + +typedef std::pair< AccessibleEventNotifier::TClientId, + AccessibleEventObject > ClientEvent; + +typedef ::comphelper::OInterfaceContainerHelper4<XAccessibleEventListener> ListenerContainer; +typedef std::map< AccessibleEventNotifier::TClientId, ListenerContainer* > ClientMap; + +/// key is the end of the interval, value is the start of the interval +typedef std::map<AccessibleEventNotifier::TClientId, + AccessibleEventNotifier::TClientId> IntervalMap; + +std::mutex& GetLocalMutex() +{ + static std::mutex MUTEX; + return MUTEX; +} + +ClientMap gaClients; + +IntervalMap& GetFreeIntervals() +{ + static IntervalMap MAP = + []() + { + IntervalMap map; + map.insert(std::make_pair( + std::numeric_limits<AccessibleEventNotifier::TClientId>::max(), 1)); + return map; + }(); + return MAP; +} + +void releaseId(AccessibleEventNotifier::TClientId const nId) +{ + IntervalMap & rFreeIntervals(GetFreeIntervals()); + IntervalMap::iterator const upper(rFreeIntervals.upper_bound(nId)); + assert(upper != rFreeIntervals.end()); + assert(nId < upper->second); // second is start of the interval! + if (nId + 1 == upper->second) + { + --upper->second; // add nId to existing interval + } + else + { + IntervalMap::iterator const lower(rFreeIntervals.lower_bound(nId)); + if (lower != rFreeIntervals.end() && lower->first == nId - 1) + { + // add nId by replacing lower with new merged entry + rFreeIntervals.insert(std::make_pair(nId, lower->second)); + rFreeIntervals.erase(lower); + } + else // otherwise just add new 1-element interval + { + rFreeIntervals.insert(std::make_pair(nId, nId)); + } + } + // currently it's not checked whether intervals can be merged now + // hopefully that won't be a problem in practice +} + +/// generates a new client id +AccessibleEventNotifier::TClientId generateId() +{ + IntervalMap & rFreeIntervals(GetFreeIntervals()); + assert(!rFreeIntervals.empty()); + IntervalMap::iterator const iter(rFreeIntervals.begin()); + AccessibleEventNotifier::TClientId const nFirst = iter->first; + AccessibleEventNotifier::TClientId const nFreeId = iter->second; + assert(nFreeId <= nFirst); + if (nFreeId != nFirst) + { + ++iter->second; // remove nFreeId from interval + } + else + { + rFreeIntervals.erase(iter); // remove 1-element interval + } + + assert(gaClients.end() == gaClients.find(nFreeId)); + + return nFreeId; +} + +/** looks up a client in our client map, asserts if it cannot find it or + no event thread is present + + @precond + to be called with our mutex locked + + @param nClient + the id of the client to lookup + @param rPos + out-parameter for the position of the client in the client map + + @return + <TRUE/> if and only if the client could be found and + <arg>rPos</arg> has been filled with its position +*/ +bool implLookupClient( + const AccessibleEventNotifier::TClientId nClient, + ClientMap::iterator& rPos ) +{ + // look up this client + ClientMap &rClients = gaClients; + rPos = rClients.find( nClient ); + assert( rClients.end() != rPos && + "AccessibleEventNotifier::implLookupClient: invalid client id " + "(did you register your client?)!" ); + + return ( rClients.end() != rPos ); +} + +} // anonymous namespace + +namespace comphelper { + +AccessibleEventNotifier::TClientId AccessibleEventNotifier::registerClient() +{ + std::scoped_lock aGuard( GetLocalMutex() ); + + // generate a new client id + TClientId nNewClientId = generateId( ); + + // the event listeners for the new client + ListenerContainer * pNewListeners = new ListenerContainer(); + // add the client + gaClients.emplace( nNewClientId, pNewListeners ); + + // outta here + return nNewClientId; +} + +void AccessibleEventNotifier::revokeClient( const TClientId _nClient ) +{ + std::scoped_lock aGuard( GetLocalMutex() ); + + ClientMap::iterator aClientPos; + if ( !implLookupClient( _nClient, aClientPos ) ) + // already asserted in implLookupClient + return; + + // remove it from the clients map + delete aClientPos->second; + gaClients.erase( aClientPos ); + releaseId(_nClient); +} + +void AccessibleEventNotifier::revokeClientNotifyDisposing( + const TClientId _nClient, const Reference< XInterface >& _rxEventSource ) +{ + std::unique_lock aGuard( GetLocalMutex() ); + + ClientMap::iterator aClientPos; + if (!implLookupClient(_nClient, aClientPos)) + // already asserted in implLookupClient + return; + + // notify the listeners + std::unique_ptr<ListenerContainer> pListeners(aClientPos->second); + + // we do not need the entry in the clients map anymore + // (do this before actually notifying, because some client + // implementations have re-entrance problems and call into + // revokeClient while we are notifying from here) + gaClients.erase(aClientPos); + releaseId(_nClient); + + // notify the "disposing" event for this client + EventObject aDisposalEvent; + aDisposalEvent.Source = _rxEventSource; + + // now really do the notification + pListeners->disposeAndClear( aGuard, aDisposalEvent ); +} + +sal_Int32 AccessibleEventNotifier::addEventListener( + const TClientId _nClient, const Reference< XAccessibleEventListener >& _rxListener ) +{ + std::unique_lock aGuard( GetLocalMutex() ); + + ClientMap::iterator aClientPos; + if ( !implLookupClient( _nClient, aClientPos ) ) + // already asserted in implLookupClient + return 0; + + if ( _rxListener.is() ) + aClientPos->second->addInterface( aGuard, _rxListener ); + + return aClientPos->second->getLength(aGuard); +} + +sal_Int32 AccessibleEventNotifier::removeEventListener( + const TClientId _nClient, const Reference< XAccessibleEventListener >& _rxListener ) +{ + std::unique_lock aGuard( GetLocalMutex() ); + + ClientMap::iterator aClientPos; + if ( !implLookupClient( _nClient, aClientPos ) ) + // already asserted in implLookupClient + return 0; + + if ( _rxListener.is() ) + aClientPos->second->removeInterface( aGuard, _rxListener ); + + return aClientPos->second->getLength(aGuard); +} + +void AccessibleEventNotifier::addEvent( const TClientId _nClient, const AccessibleEventObject& _rEvent ) +{ + std::vector< Reference< XAccessibleEventListener > > aListeners; + + { + std::unique_lock aGuard( GetLocalMutex() ); + + ClientMap::iterator aClientPos; + if ( !implLookupClient( _nClient, aClientPos ) ) + // already asserted in implLookupClient + return; + + // since we're synchronous, again, we want to notify immediately + aListeners = aClientPos->second->getElements(aGuard); + } + + // default handling: loop through all listeners, and notify them + for ( const auto& rListener : aListeners ) + { + try + { + rListener->notifyEvent( _rEvent ); + } + catch( const Exception& ) + { + // no assertion, because a broken access remote bridge or something like this + // can cause this exception + } + } +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/accessiblekeybindinghelper.cxx b/comphelper/source/misc/accessiblekeybindinghelper.cxx new file mode 100644 index 000000000..d1db69b98 --- /dev/null +++ b/comphelper/source/misc/accessiblekeybindinghelper.cxx @@ -0,0 +1,97 @@ +/* -*- 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 <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <comphelper/accessiblekeybindinghelper.hxx> +#include <o3tl/safeint.hxx> + + +namespace comphelper +{ + + + using namespace ::com::sun::star; // MT 04/2003: was ::drafts::com::sun::star - otherwise too many changes + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::accessibility; + + + // OAccessibleKeyBindingHelper + + + OAccessibleKeyBindingHelper::OAccessibleKeyBindingHelper() + { + } + + + OAccessibleKeyBindingHelper::OAccessibleKeyBindingHelper( const OAccessibleKeyBindingHelper& rHelper ) + : cppu::WeakImplHelper<XAccessibleKeyBinding>( rHelper ) + , m_aKeyBindings( rHelper.m_aKeyBindings ) + { + } + + + OAccessibleKeyBindingHelper::~OAccessibleKeyBindingHelper() + { + } + + + void OAccessibleKeyBindingHelper::AddKeyBinding( const Sequence< awt::KeyStroke >& rKeyBinding ) + { + std::scoped_lock aGuard( m_aMutex ); + + m_aKeyBindings.push_back( rKeyBinding ); + } + + + void OAccessibleKeyBindingHelper::AddKeyBinding( const awt::KeyStroke& rKeyStroke ) + { + std::scoped_lock aGuard( m_aMutex ); + m_aKeyBindings.push_back( { rKeyStroke } ); + } + + + // XAccessibleKeyBinding + + + sal_Int32 OAccessibleKeyBindingHelper::getAccessibleKeyBindingCount() + { + std::scoped_lock aGuard( m_aMutex ); + + return m_aKeyBindings.size(); + } + + + Sequence< awt::KeyStroke > OAccessibleKeyBindingHelper::getAccessibleKeyBinding( sal_Int32 nIndex ) + { + std::scoped_lock aGuard( m_aMutex ); + + if ( nIndex < 0 || o3tl::make_unsigned(nIndex) >= m_aKeyBindings.size() ) + throw IndexOutOfBoundsException(); + + return m_aKeyBindings[nIndex]; + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/accessibleselectionhelper.cxx b/comphelper/source/misc/accessibleselectionhelper.cxx new file mode 100644 index 000000000..967c1b079 --- /dev/null +++ b/comphelper/source/misc/accessibleselectionhelper.cxx @@ -0,0 +1,172 @@ +/* -*- 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/accessibleselectionhelper.hxx> + + +namespace comphelper +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::accessibility; + + OCommonAccessibleSelection::OCommonAccessibleSelection( ) + { + } + + OCommonAccessibleSelection::~OCommonAccessibleSelection() {} + + + void OCommonAccessibleSelection::selectAccessibleChild( sal_Int32 nChildIndex ) + { + implSelect( nChildIndex, true ); + } + + + bool OCommonAccessibleSelection::isAccessibleChildSelected( sal_Int32 nChildIndex ) + { + return implIsSelected( nChildIndex ); + } + + + void OCommonAccessibleSelection::clearAccessibleSelection( ) + { + implSelect( ACCESSIBLE_SELECTION_CHILD_ALL, false ); + } + + + void OCommonAccessibleSelection::selectAllAccessibleChildren( ) + { + implSelect( ACCESSIBLE_SELECTION_CHILD_ALL, true ); + } + + + sal_Int32 OCommonAccessibleSelection::getSelectedAccessibleChildCount( ) + { + sal_Int32 nRet = 0; + Reference< XAccessibleContext > xParentContext( implGetAccessibleContext() ); + + OSL_ENSURE( xParentContext.is(), "OCommonAccessibleSelection::getSelectedAccessibleChildCount: no parent context!" ); + + if( xParentContext.is() ) + { + for( sal_Int32 i = 0, nChildCount = xParentContext->getAccessibleChildCount(); i < nChildCount; i++ ) + if( implIsSelected( i ) ) + ++nRet; + } + + return nRet; + } + + + Reference< XAccessible > OCommonAccessibleSelection::getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) + { + Reference< XAccessible > xRet; + Reference< XAccessibleContext > xParentContext( implGetAccessibleContext() ); + + OSL_ENSURE( xParentContext.is(), "OCommonAccessibleSelection::getSelectedAccessibleChildCount: no parent context!" ); + + if( xParentContext.is() ) + { + for( sal_Int32 i = 0, nChildCount = xParentContext->getAccessibleChildCount(), nPos = 0; ( i < nChildCount ) && !xRet.is(); i++ ) + if( implIsSelected( i ) && ( nPos++ == nSelectedChildIndex ) ) + xRet = xParentContext->getAccessibleChild( i ); + } + + return xRet; + } + + + void OCommonAccessibleSelection::deselectAccessibleChild( sal_Int32 nSelectedChildIndex ) + { + implSelect( nSelectedChildIndex, false ); + } + + OAccessibleSelectionHelper::OAccessibleSelectionHelper() + { + } + + + IMPLEMENT_FORWARD_XINTERFACE2( OAccessibleSelectionHelper, OAccessibleComponentHelper, OAccessibleSelectionHelper_Base ) + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OAccessibleSelectionHelper, OAccessibleComponentHelper, OAccessibleSelectionHelper_Base ) + // (order matters: the first is the class name, the second is the class doing the ref counting) + + + Reference< XAccessibleContext > OAccessibleSelectionHelper::implGetAccessibleContext() + { + return this; + } + + + void SAL_CALL OAccessibleSelectionHelper::selectAccessibleChild( sal_Int32 nChildIndex ) + { + OExternalLockGuard aGuard( this ); + OCommonAccessibleSelection::selectAccessibleChild( nChildIndex ); + } + + + sal_Bool SAL_CALL OAccessibleSelectionHelper::isAccessibleChildSelected( sal_Int32 nChildIndex ) + { + OExternalLockGuard aGuard( this ); + return OCommonAccessibleSelection::isAccessibleChildSelected( nChildIndex ); + } + + + void SAL_CALL OAccessibleSelectionHelper::clearAccessibleSelection( ) + { + OExternalLockGuard aGuard( this ); + OCommonAccessibleSelection::clearAccessibleSelection(); + } + + + void SAL_CALL OAccessibleSelectionHelper::selectAllAccessibleChildren( ) + { + OExternalLockGuard aGuard( this ); + OCommonAccessibleSelection::selectAllAccessibleChildren(); + } + + + sal_Int32 SAL_CALL OAccessibleSelectionHelper::getSelectedAccessibleChildCount( ) + { + OExternalLockGuard aGuard( this ); + return OCommonAccessibleSelection::getSelectedAccessibleChildCount(); + } + + + Reference< XAccessible > SAL_CALL OAccessibleSelectionHelper::getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) + { + OExternalLockGuard aGuard( this ); + return OCommonAccessibleSelection::getSelectedAccessibleChild( nSelectedChildIndex ); + } + + + void SAL_CALL OAccessibleSelectionHelper::deselectAccessibleChild( sal_Int32 nSelectedChildIndex ) + { + OExternalLockGuard aGuard( this ); + OCommonAccessibleSelection::deselectAccessibleChild( nSelectedChildIndex ); + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/accessibletexthelper.cxx b/comphelper/source/misc/accessibletexthelper.cxx new file mode 100644 index 000000000..27747b3bc --- /dev/null +++ b/comphelper/source/misc/accessibletexthelper.cxx @@ -0,0 +1,800 @@ +/* -*- 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/accessibletexthelper.hxx> +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/CharacterClassification.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/KCharacterType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/accessibility/TextSegment.hpp> + +#include <algorithm> + + +namespace comphelper +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::accessibility; + + + // OCommonAccessibleText + + + OCommonAccessibleText::OCommonAccessibleText() + { + } + + + OCommonAccessibleText::~OCommonAccessibleText() + { + } + + + Reference < i18n::XBreakIterator > const & OCommonAccessibleText::implGetBreakIterator() + { + if ( !m_xBreakIter.is() ) + { + Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + m_xBreakIter = i18n::BreakIterator::create(xContext); + } + + return m_xBreakIter; + } + + + Reference < i18n::XCharacterClassification > const & OCommonAccessibleText::implGetCharacterClassification() + { + if ( !m_xCharClass.is() ) + { + m_xCharClass = i18n::CharacterClassification::create( ::comphelper::getProcessComponentContext() ); + } + + return m_xCharClass; + } + + + bool OCommonAccessibleText::implIsValidBoundary( i18n::Boundary const & rBoundary, sal_Int32 nLength ) + { + return ( rBoundary.startPos >= 0 ) && ( rBoundary.startPos < nLength ) && ( rBoundary.endPos >= 0 ) && ( rBoundary.endPos <= nLength ); + } + + + bool OCommonAccessibleText::implIsValidIndex( sal_Int32 nIndex, sal_Int32 nLength ) + { + return ( nIndex >= 0 ) && ( nIndex < nLength ); + } + + + bool OCommonAccessibleText::implIsValidRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex, sal_Int32 nLength ) + { + return ( nStartIndex >= 0 ) && ( nStartIndex <= nLength ) && ( nEndIndex >= 0 ) && ( nEndIndex <= nLength ); + } + + + void OCommonAccessibleText::implGetGlyphBoundary( const OUString& rText, i18n::Boundary& rBoundary, sal_Int32 nIndex ) + { + if ( implIsValidIndex( nIndex, rText.getLength() ) ) + { + Reference < i18n::XBreakIterator > xBreakIter = implGetBreakIterator(); + if ( xBreakIter.is() ) + { + sal_Int32 nCount = 1; + sal_Int32 nDone; + sal_Int32 nStartIndex = xBreakIter->previousCharacters( rText, nIndex, implGetLocale(), i18n::CharacterIteratorMode::SKIPCELL, nCount, nDone ); + if ( nDone != 0 ) + nStartIndex = xBreakIter->nextCharacters( rText, nStartIndex, implGetLocale(), i18n::CharacterIteratorMode::SKIPCELL, nCount, nDone ); + sal_Int32 nEndIndex = xBreakIter->nextCharacters( rText, nStartIndex, implGetLocale(), i18n::CharacterIteratorMode::SKIPCELL, nCount, nDone ); + if ( nDone != 0 ) + { + rBoundary.startPos = nStartIndex; + rBoundary.endPos = nEndIndex; + } + } + } + else + { + rBoundary.startPos = nIndex; + rBoundary.endPos = nIndex; + } + } + + + bool OCommonAccessibleText::implGetWordBoundary( const OUString& rText, i18n::Boundary& rBoundary, sal_Int32 nIndex ) + { + bool bWord = false; + + if ( implIsValidIndex( nIndex, rText.getLength() ) ) + { + Reference < i18n::XBreakIterator > xBreakIter = implGetBreakIterator(); + if ( xBreakIter.is() ) + { + rBoundary = xBreakIter->getWordBoundary( rText, nIndex, implGetLocale(), i18n::WordType::ANY_WORD, true ); + + // it's a word, if the first character is an alpha-numeric character + Reference< i18n::XCharacterClassification > xCharClass = implGetCharacterClassification(); + if ( xCharClass.is() ) + { + sal_Int32 nType = xCharClass->getCharacterType( rText, rBoundary.startPos, implGetLocale() ); + if ( ( nType & ( i18n::KCharacterType::LETTER | i18n::KCharacterType::DIGIT ) ) != 0 ) + bWord = true; + } + } + } + else + { + rBoundary.startPos = nIndex; + rBoundary.endPos = nIndex; + } + + return bWord; + } + + + void OCommonAccessibleText::implGetSentenceBoundary( const OUString& rText, i18n::Boundary& rBoundary, sal_Int32 nIndex ) + { + if ( implIsValidIndex( nIndex, rText.getLength() ) ) + { + Locale aLocale = implGetLocale(); + Reference < i18n::XBreakIterator > xBreakIter = implGetBreakIterator(); + if ( xBreakIter.is() ) + { + rBoundary.endPos = xBreakIter->endOfSentence( rText, nIndex, aLocale ); + rBoundary.startPos = xBreakIter->beginOfSentence( rText, rBoundary.endPos, aLocale ); + } + } + else + { + rBoundary.startPos = nIndex; + rBoundary.endPos = nIndex; + } + } + + + void OCommonAccessibleText::implGetParagraphBoundary( const OUString& rText, i18n::Boundary& rBoundary, sal_Int32 nIndex ) + { + if ( implIsValidIndex( nIndex, rText.getLength() ) ) + { + rBoundary.startPos = 0; + rBoundary.endPos = rText.getLength(); + + sal_Int32 nFound = rText.lastIndexOf( '\n', nIndex ); + if ( nFound != -1 ) + rBoundary.startPos = nFound + 1; + + nFound = rText.indexOf( '\n', nIndex ); + if ( nFound != -1 ) + rBoundary.endPos = nFound + 1; + } + else + { + rBoundary.startPos = nIndex; + rBoundary.endPos = nIndex; + } + } + + + void OCommonAccessibleText::implGetLineBoundary( const OUString& rText, i18n::Boundary& rBoundary, sal_Int32 nIndex ) + { + sal_Int32 nLength = rText.getLength(); + + if ( implIsValidIndex( nIndex, nLength ) || nIndex == nLength ) + { + rBoundary.startPos = 0; + rBoundary.endPos = nLength; + } + else + { + rBoundary.startPos = nIndex; + rBoundary.endPos = nIndex; + } + } + + + sal_Unicode OCommonAccessibleText::implGetCharacter( std::u16string_view rText, sal_Int32 nIndex ) + { + if ( !implIsValidIndex( nIndex, rText.size() ) ) + throw IndexOutOfBoundsException(); + + return rText[nIndex]; + } + + OUString OCommonAccessibleText::getSelectedText() + { + OUString sText; + sal_Int32 nStartIndex; + sal_Int32 nEndIndex; + + implGetSelection( nStartIndex, nEndIndex ); + + try + { + sText = implGetTextRange( implGetText(), nStartIndex, nEndIndex ); + } + catch ( IndexOutOfBoundsException& ) + { + } + + return sText; + } + + + sal_Int32 OCommonAccessibleText::getSelectionStart() + { + sal_Int32 nStartIndex; + sal_Int32 nEndIndex; + + implGetSelection( nStartIndex, nEndIndex ); + + return nStartIndex; + } + + + sal_Int32 OCommonAccessibleText::getSelectionEnd() + { + sal_Int32 nStartIndex; + sal_Int32 nEndIndex; + + implGetSelection( nStartIndex, nEndIndex ); + + return nEndIndex; + } + + + OUString OCommonAccessibleText::implGetTextRange( std::u16string_view rText, sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + + if ( !implIsValidRange( nStartIndex, nEndIndex, rText.size() ) ) + throw IndexOutOfBoundsException(); + + sal_Int32 nMinIndex = std::min( nStartIndex, nEndIndex ); + sal_Int32 nMaxIndex = std::max( nStartIndex, nEndIndex ); + + return OUString(rText.substr( nMinIndex, nMaxIndex - nMinIndex )); + } + + TextSegment OCommonAccessibleText::getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + OUString sText( implGetText() ); + sal_Int32 nLength = sText.getLength(); + + if ( !implIsValidIndex( nIndex, nLength ) && nIndex != nLength ) + throw IndexOutOfBoundsException(); + + i18n::Boundary aBoundary; + TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + switch ( aTextType ) + { + case AccessibleTextType::CHARACTER: + { + if ( implIsValidIndex( nIndex, nLength ) ) + { + aResult.SegmentText = sText.copy( nIndex, 1 ); + aResult.SegmentStart = nIndex; + aResult.SegmentEnd = nIndex+1; + } + } + break; + case AccessibleTextType::GLYPH: + { + // get glyph at index + implGetGlyphBoundary( sText, aBoundary, nIndex ); + if ( implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + break; + case AccessibleTextType::WORD: + { + // get word at index + bool bWord = implGetWordBoundary( sText, aBoundary, nIndex ); + if ( bWord && implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + break; + case AccessibleTextType::SENTENCE: + { + // get sentence at index + implGetSentenceBoundary( sText, aBoundary, nIndex ); + if ( implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + break; + case AccessibleTextType::PARAGRAPH: + { + // get paragraph at index + implGetParagraphBoundary( sText, aBoundary, nIndex ); + if ( implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + break; + case AccessibleTextType::LINE: + { + // get line at index + implGetLineBoundary( sText, aBoundary, nIndex ); + if ( implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + break; + case AccessibleTextType::ATTRIBUTE_RUN: + { + // TODO: implGetAttributeRunBoundary() (incompatible!) + + aResult.SegmentText = sText; + aResult.SegmentStart = 0; + aResult.SegmentEnd = nLength; + } + break; + default: + { + // unknown text type + } + } + + return aResult; + } + + + TextSegment OCommonAccessibleText::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + OUString sText( implGetText() ); + sal_Int32 nLength = sText.getLength(); + + if ( !implIsValidIndex( nIndex, nLength ) && nIndex != nLength ) + throw IndexOutOfBoundsException(); + + i18n::Boundary aBoundary; + TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + switch ( aTextType ) + { + case AccessibleTextType::CHARACTER: + { + if ( implIsValidIndex( nIndex - 1, nLength ) ) + { + aResult.SegmentText = sText.copy( nIndex - 1, 1 ); + aResult.SegmentStart = nIndex-1; + aResult.SegmentEnd = nIndex; + } + } + break; + case AccessibleTextType::GLYPH: + { + // get glyph at index + implGetGlyphBoundary( sText, aBoundary, nIndex ); + // get previous glyph + if ( aBoundary.startPos > 0 ) + { + implGetGlyphBoundary( sText, aBoundary, aBoundary.startPos - 1 ); + if ( implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + } + break; + case AccessibleTextType::WORD: + { + // get word at index + implGetWordBoundary( sText, aBoundary, nIndex ); + // get previous word + bool bWord = false; + while ( !bWord && aBoundary.startPos > 0 ) + bWord = implGetWordBoundary( sText, aBoundary, aBoundary.startPos - 1 ); + if ( bWord && implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + break; + case AccessibleTextType::SENTENCE: + { + // get sentence at index + implGetSentenceBoundary( sText, aBoundary, nIndex ); + // get previous sentence + if ( aBoundary.startPos > 0 ) + { + implGetSentenceBoundary( sText, aBoundary, aBoundary.startPos - 1 ); + if ( implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + } + break; + case AccessibleTextType::PARAGRAPH: + { + // get paragraph at index + implGetParagraphBoundary( sText, aBoundary, nIndex ); + // get previous paragraph + if ( aBoundary.startPos > 0 ) + { + implGetParagraphBoundary( sText, aBoundary, aBoundary.startPos - 1 ); + if ( implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + } + break; + case AccessibleTextType::LINE: + { + // get line at index + implGetLineBoundary( sText, aBoundary, nIndex ); + // get previous line + if ( aBoundary.startPos > 0 ) + { + implGetLineBoundary( sText, aBoundary, aBoundary.startPos - 1 ); + if ( implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + } + break; + case AccessibleTextType::ATTRIBUTE_RUN: + { + // TODO: implGetAttributeRunBoundary() (incompatible!) + } + break; + default: + { + // unknown text type + } + } + + return aResult; + } + + + TextSegment OCommonAccessibleText::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + OUString sText( implGetText() ); + sal_Int32 nLength = sText.getLength(); + + if ( !implIsValidIndex( nIndex, nLength ) && nIndex != nLength ) + throw IndexOutOfBoundsException(); + + i18n::Boundary aBoundary; + TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + switch ( aTextType ) + { + case AccessibleTextType::CHARACTER: + { + if ( implIsValidIndex( nIndex + 1, nLength ) ) + { + aResult.SegmentText = sText.copy( nIndex + 1, 1 ); + aResult.SegmentStart = nIndex+1; + aResult.SegmentEnd = nIndex+2; + } + } + break; + case AccessibleTextType::GLYPH: + { + // get glyph at index + implGetGlyphBoundary( sText, aBoundary, nIndex ); + // get next glyph + if ( aBoundary.endPos < nLength ) + { + implGetGlyphBoundary( sText, aBoundary, aBoundary.endPos ); + if ( implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + } + break; + case AccessibleTextType::WORD: + { + // get word at index + implGetWordBoundary( sText, aBoundary, nIndex ); + // get next word + bool bWord = false; + while ( !bWord && aBoundary.endPos < nLength ) + bWord = implGetWordBoundary( sText, aBoundary, aBoundary.endPos ); + if ( bWord && implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + break; + case AccessibleTextType::SENTENCE: + { + // get sentence at index + implGetSentenceBoundary( sText, aBoundary, nIndex ); + // get next sentence + sal_Int32 nEnd = aBoundary.endPos; + sal_Int32 nI = aBoundary.endPos; + bool bFound = false; + while ( !bFound && ++nI < nLength ) + { + implGetSentenceBoundary( sText, aBoundary, nI ); + bFound = ( aBoundary.endPos > nEnd ); + } + if ( bFound && implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + break; + case AccessibleTextType::PARAGRAPH: + { + // get paragraph at index + implGetParagraphBoundary( sText, aBoundary, nIndex ); + // get next paragraph + if ( aBoundary.endPos < nLength ) + { + implGetParagraphBoundary( sText, aBoundary, aBoundary.endPos ); + if ( implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + } + break; + case AccessibleTextType::LINE: + { + // get line at index + implGetLineBoundary( sText, aBoundary, nIndex ); + // get next line + if ( aBoundary.endPos < nLength ) + { + implGetLineBoundary( sText, aBoundary, aBoundary.endPos ); + if ( implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + } + } + } + break; + case AccessibleTextType::ATTRIBUTE_RUN: + { + // TODO: implGetAttributeRunBoundary() (incompatible!) + } + break; + default: + { + // unknown text type + } + } + + return aResult; + } + + + bool OCommonAccessibleText::implInitTextChangedEvent( + std::u16string_view rOldString, + std::u16string_view rNewString, + css::uno::Any& rDeleted, + css::uno::Any& rInserted) // throw() + { + size_t nLenOld = rOldString.size(); + size_t nLenNew = rNewString.size(); + + // equal + if ((0 == nLenOld) && (0 == nLenNew)) + return false; + + TextSegment aDeletedText; + TextSegment aInsertedText; + + aDeletedText.SegmentStart = -1; + aDeletedText.SegmentEnd = -1; + aInsertedText.SegmentStart = -1; + aInsertedText.SegmentEnd = -1; + + // insert only + if ((0 == nLenOld) && (nLenNew > 0)) + { + aInsertedText.SegmentStart = 0; + aInsertedText.SegmentEnd = nLenNew; + aInsertedText.SegmentText = rNewString.substr( aInsertedText.SegmentStart, aInsertedText.SegmentEnd - aInsertedText.SegmentStart ); + + rInserted <<= aInsertedText; + return true; + } + + // delete only + if ((nLenOld > 0) && (0 == nLenNew)) + { + aDeletedText.SegmentStart = 0; + aDeletedText.SegmentEnd = nLenOld; + aDeletedText.SegmentText = rOldString.substr( aDeletedText.SegmentStart, aDeletedText.SegmentEnd - aDeletedText.SegmentStart ); + + rDeleted <<= aDeletedText; + return true; + } + + auto pFirstDiffOld = rOldString.begin(); + auto pLastDiffOld = rOldString.end(); + auto pFirstDiffNew = rNewString.begin(); + auto pLastDiffNew = rNewString.end(); + + // find first difference + while ((pFirstDiffOld < pLastDiffOld) && (pFirstDiffNew < pLastDiffNew) + && (*pFirstDiffOld == *pFirstDiffNew)) + { + pFirstDiffOld++; + pFirstDiffNew++; + } + + // equality test + if (pFirstDiffOld == pLastDiffOld && pFirstDiffNew == pLastDiffNew) + return false; + + // find last difference + while ( ( pLastDiffOld > pFirstDiffOld) && + ( pLastDiffNew > pFirstDiffNew) && + (pLastDiffOld[-1] == pLastDiffNew[-1])) + { + pLastDiffOld--; + pLastDiffNew--; + } + + if (pFirstDiffOld < pLastDiffOld) + { + aDeletedText.SegmentStart = pFirstDiffOld - rOldString.begin(); + aDeletedText.SegmentEnd = pLastDiffOld - rOldString.begin(); + aDeletedText.SegmentText = rOldString.substr( aDeletedText.SegmentStart, aDeletedText.SegmentEnd - aDeletedText.SegmentStart ); + + rDeleted <<= aDeletedText; + } + + if (pFirstDiffNew < pLastDiffNew) + { + aInsertedText.SegmentStart = pFirstDiffNew - rNewString.begin(); + aInsertedText.SegmentEnd = pLastDiffNew - rNewString.begin(); + aInsertedText.SegmentText = rNewString.substr( aInsertedText.SegmentStart, aInsertedText.SegmentEnd - aInsertedText.SegmentStart ); + + rInserted <<= aInsertedText; + } + return true; + } + + + // OAccessibleTextHelper + + + OAccessibleTextHelper::OAccessibleTextHelper( ) + { + } + + + // XInterface + + + IMPLEMENT_FORWARD_XINTERFACE2( OAccessibleTextHelper, OAccessibleExtendedComponentHelper, OAccessibleTextHelper_Base ) + + + // XTypeProvider + + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OAccessibleTextHelper, OAccessibleExtendedComponentHelper, OAccessibleTextHelper_Base ) + + + // XAccessibleText + + + OUString OAccessibleTextHelper::getSelectedText() + { + OExternalLockGuard aGuard( this ); + + return OCommonAccessibleText::getSelectedText(); + } + + + sal_Int32 OAccessibleTextHelper::getSelectionStart() + { + OExternalLockGuard aGuard( this ); + + return OCommonAccessibleText::getSelectionStart(); + } + + + sal_Int32 OAccessibleTextHelper::getSelectionEnd() + { + OExternalLockGuard aGuard( this ); + + return OCommonAccessibleText::getSelectionEnd(); + } + + + TextSegment OAccessibleTextHelper::getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + OExternalLockGuard aGuard( this ); + + return OCommonAccessibleText::getTextAtIndex( nIndex, aTextType ); + } + + + TextSegment OAccessibleTextHelper::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + OExternalLockGuard aGuard( this ); + + return OCommonAccessibleText::getTextBeforeIndex( nIndex, aTextType ); + } + + + TextSegment OAccessibleTextHelper::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + OExternalLockGuard aGuard( this ); + + return OCommonAccessibleText::getTextBehindIndex( nIndex, aTextType ); + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/accessiblewrapper.cxx b/comphelper/source/misc/accessiblewrapper.cxx new file mode 100644 index 000000000..1167f5640 --- /dev/null +++ b/comphelper/source/misc/accessiblewrapper.cxx @@ -0,0 +1,616 @@ +/* -*- 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/accessiblewrapper.hxx> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> + +using namespace ::comphelper; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; + + +namespace comphelper +{ + OWrappedAccessibleChildrenManager::OWrappedAccessibleChildrenManager( const Reference< XComponentContext >& _rxContext ) + :m_xContext( _rxContext ) + ,m_bTransientChildren( true ) + { + } + + + OWrappedAccessibleChildrenManager::~OWrappedAccessibleChildrenManager( ) + { + } + + + void OWrappedAccessibleChildrenManager::setTransientChildren( bool _bSet ) + { + m_bTransientChildren = _bSet; + } + + + void OWrappedAccessibleChildrenManager::setOwningAccessible( const Reference< XAccessible >& _rxAcc ) + { + OSL_ENSURE( !m_aOwningAccessible.get().is(), "OWrappedAccessibleChildrenManager::setOwningAccessible: to be called only once!" ); + m_aOwningAccessible = WeakReference< XAccessible >( _rxAcc ); + } + + + void OWrappedAccessibleChildrenManager::removeFromCache( const Reference< XAccessible >& _rxKey ) + { + AccessibleMap::iterator aRemovedPos = m_aChildrenMap.find( _rxKey ); + if ( m_aChildrenMap.end() != aRemovedPos ) + { // it was cached + // remove ourself as event listener + Reference< XComponent > xComp( aRemovedPos->first, UNO_QUERY ); + if( xComp.is() ) + xComp->removeEventListener( this ); + // and remove the entry from the map + m_aChildrenMap.erase( aRemovedPos ); + } + } + + + void OWrappedAccessibleChildrenManager::invalidateAll( ) + { + // remove as event listener from the map elements + for( const auto& rChild : m_aChildrenMap ) + { + Reference< XComponent > xComp( rChild.first, UNO_QUERY ); + if( xComp.is() ) + xComp->removeEventListener( this ); + } + // clear the map + AccessibleMap().swap(m_aChildrenMap); + } + + + Reference< XAccessible > OWrappedAccessibleChildrenManager::getAccessibleWrapperFor( + const Reference< XAccessible >& _rxKey ) + { + rtl::Reference< OAccessibleWrapper > xValue; + + if( !_rxKey.is() ) + { + // fprintf( stderr, "It was this path that was crashing stuff\n" ); + return xValue; + } + + // do we have this child in the cache? + AccessibleMap::const_iterator aPos = m_aChildrenMap.find( _rxKey ); + if ( m_aChildrenMap.end() != aPos ) + { + xValue = aPos->second; + } + else + { // not found in the cache, and allowed to create + // -> new wrapper + xValue = new OAccessibleWrapper( m_xContext, _rxKey, m_aOwningAccessible ); + + // see if we do cache children + if ( !m_bTransientChildren ) + { + if (!m_aChildrenMap.emplace( _rxKey, xValue ).second) + { + OSL_FAIL( + "OWrappedAccessibleChildrenManager::" + "getAccessibleWrapperFor: element was already" + " inserted!" ); + } + + // listen for disposals of inner children - this may happen when the inner context + // is the owner for the inner children (it will dispose these children, and of course + // not our wrapper for these children) + Reference< XComponent > xComp( _rxKey, UNO_QUERY ); + if ( xComp.is() ) + xComp->addEventListener( this ); + } + } + + return xValue; + } + + + void OWrappedAccessibleChildrenManager::dispose() + { + // dispose our children + for( const auto& rChild : m_aChildrenMap ) + { + Reference< XComponent > xComp( rChild.first, UNO_QUERY ); + if( xComp.is() ) + xComp->removeEventListener( this ); + + Reference< XComponent > xContextComponent; + if( rChild.second.is() ) + xContextComponent.set( rChild.second->getContextNoCreate(), + ::css::uno::UNO_QUERY ); + if( xContextComponent.is() ) + xContextComponent->dispose(); + } + + // clear our children + AccessibleMap().swap(m_aChildrenMap); + } + + + void OWrappedAccessibleChildrenManager::implTranslateChildEventValue( const Any& _rInValue, Any& _rOutValue ) + { + _rOutValue.clear(); + Reference< XAccessible > xChild; + if ( _rInValue >>= xChild ) + _rOutValue <<= getAccessibleWrapperFor( xChild ); + } + + + void OWrappedAccessibleChildrenManager::translateAccessibleEvent( const AccessibleEventObject& _rEvent, AccessibleEventObject& _rTranslatedEvent ) + { + // just in case we can't translate some of the values: + _rTranslatedEvent.NewValue = _rEvent.NewValue; + _rTranslatedEvent.OldValue = _rEvent.OldValue; + + switch ( _rEvent.EventId ) + { + case AccessibleEventId::CHILD: + case AccessibleEventId::ACTIVE_DESCENDANT_CHANGED: + case AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED: + case AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED: + case AccessibleEventId::LABEL_FOR_RELATION_CHANGED: + case AccessibleEventId::LABELED_BY_RELATION_CHANGED: + case AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED: + case AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED: + // these are events where both the old and the new value contain child references + implTranslateChildEventValue( _rEvent.OldValue, _rTranslatedEvent.OldValue ); + implTranslateChildEventValue( _rEvent.NewValue, _rTranslatedEvent.NewValue ); + break; + + case AccessibleEventId::NAME_CHANGED: + case AccessibleEventId::DESCRIPTION_CHANGED: + case AccessibleEventId::ACTION_CHANGED: + case AccessibleEventId::STATE_CHANGED: + case AccessibleEventId::BOUNDRECT_CHANGED: + case AccessibleEventId::INVALIDATE_ALL_CHILDREN: + case AccessibleEventId::SELECTION_CHANGED: + case AccessibleEventId::VISIBLE_DATA_CHANGED: + case AccessibleEventId::VALUE_CHANGED: + case AccessibleEventId::MEMBER_OF_RELATION_CHANGED: + case AccessibleEventId::CARET_CHANGED: + case AccessibleEventId::TEXT_CHANGED: + case AccessibleEventId::HYPERTEXT_CHANGED: + case AccessibleEventId::TABLE_CAPTION_CHANGED: + case AccessibleEventId::TABLE_COLUMN_DESCRIPTION_CHANGED: + case AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED: + case AccessibleEventId::TABLE_MODEL_CHANGED: + case AccessibleEventId::TABLE_ROW_DESCRIPTION_CHANGED: + case AccessibleEventId::TABLE_ROW_HEADER_CHANGED: + case AccessibleEventId::TABLE_SUMMARY_CHANGED: + // these Ids are also missed: SUB_WINDOW_OF_RELATION_CHANGED & TEXT_ATTRIBUTE_CHANGED + case AccessibleEventId::TEXT_SELECTION_CHANGED: + // nothing to translate + break; + + default: + OSL_FAIL( "OWrappedAccessibleChildrenManager::translateAccessibleEvent: unknown (or unexpected) event id!" ); + break; + } + } + + + void OWrappedAccessibleChildrenManager::handleChildNotification( const AccessibleEventObject& _rEvent ) + { + if ( AccessibleEventId::INVALIDATE_ALL_CHILDREN == _rEvent.EventId ) + { // clear our child map + invalidateAll( ); + } + else if ( AccessibleEventId::CHILD == _rEvent.EventId ) + { + // check if the removed or replaced element is cached + Reference< XAccessible > xRemoved; + if ( _rEvent.OldValue >>= xRemoved ) + removeFromCache( xRemoved ); + } + } + + + void SAL_CALL OWrappedAccessibleChildrenManager::disposing( const EventObject& _rSource ) + { + // this should come from one of the inner XAccessible's of our children + Reference< XAccessible > xSource( _rSource.Source, UNO_QUERY ); + AccessibleMap::iterator aDisposedPos = m_aChildrenMap.find( xSource ); +#if OSL_DEBUG_LEVEL > 0 + if ( m_aChildrenMap.end() == aDisposedPos ) + { + OSL_FAIL( "OWrappedAccessibleChildrenManager::disposing: where did this come from?" ); + // helper for diagnostics + Reference< XAccessible > xOwningAccessible( m_aOwningAccessible ); + Reference< XAccessibleContext > xContext; + try + { + if ( xOwningAccessible.is() ) + xContext = xOwningAccessible->getAccessibleContext(); + if ( xContext.is() ) + { + //TODO: do something + //OUString sName = xContext->getAccessibleName(); + //OUString sDescription = xContext->getAccessibleDescription(); + //sal_Int32 nPlaceYourBreakpointHere = 0; + } + } + catch( const Exception& /*e*/ ) + { + // silent this, it's only diagnostics which failed + } + } +#endif + if ( m_aChildrenMap.end() != aDisposedPos ) + { + m_aChildrenMap.erase( aDisposedPos ); + } + } + + OAccessibleWrapper::OAccessibleWrapper( const Reference< XComponentContext >& _rxContext, + const Reference< XAccessible >& _rxInnerAccessible, const Reference< XAccessible >& _rxParentAccessible ) + :OAccessibleWrapper_Base( ) + ,OComponentProxyAggregation( _rxContext, Reference< XComponent >( _rxInnerAccessible, UNO_QUERY ) ) + ,m_xParentAccessible( _rxParentAccessible ) + ,m_xInnerAccessible( _rxInnerAccessible ) + { + } + + + OAccessibleWrapper::~OAccessibleWrapper( ) + { + if ( !m_rBHelper.bDisposed ) + { + acquire(); // to prevent duplicate dtor calls + dispose(); + } + } + + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OAccessibleWrapper, OComponentProxyAggregation, OAccessibleWrapper_Base ) + IMPLEMENT_FORWARD_REFCOUNT( OAccessibleWrapper, OComponentProxyAggregation ) + + + Any OAccessibleWrapper::queryInterface( const Type& _rType ) + { + // #111089# instead of the inner XAccessible the proxy XAccessible must be returned + Any aReturn = OAccessibleWrapper_Base::queryInterface( _rType ); + if ( !aReturn.hasValue() ) + aReturn = OComponentProxyAggregation::queryInterface( _rType ); + + return aReturn; + } + + + Reference< XAccessibleContext > OAccessibleWrapper::getContextNoCreate( ) const + { + return m_aContext; + } + + + rtl::Reference<OAccessibleContextWrapper> OAccessibleWrapper::createAccessibleContext( const Reference< XAccessibleContext >& _rxInnerContext ) + { + return new OAccessibleContextWrapper( getComponentContext(), _rxInnerContext, this, m_xParentAccessible ); + } + + + Reference< XAccessibleContext > SAL_CALL OAccessibleWrapper::getAccessibleContext( ) + { + // see if the context is still alive (we cache it) + Reference< XAccessibleContext > xContext = m_aContext; + if ( !xContext.is() ) + { + // create a new context + Reference< XAccessibleContext > xInnerContext = m_xInnerAccessible->getAccessibleContext( ); + if ( xInnerContext.is() ) + { + xContext = createAccessibleContext( xInnerContext ); + // cache it + m_aContext = WeakReference< XAccessibleContext >( xContext ); + } + } + + return xContext; + } + + OAccessibleContextWrapperHelper::OAccessibleContextWrapperHelper( + const Reference< XComponentContext >& _rxContext, + ::cppu::OBroadcastHelper& _rBHelper, + const Reference< XAccessibleContext >& _rxInnerAccessibleContext, + const Reference< XAccessible >& _rxOwningAccessible, + const Reference< XAccessible >& _rxParentAccessible ) + :OComponentProxyAggregationHelper( _rxContext, _rBHelper ) + ,m_xInnerContext( _rxInnerAccessibleContext ) + ,m_xOwningAccessible( _rxOwningAccessible ) + ,m_xParentAccessible( _rxParentAccessible ) + // initialize the mapper for our children + ,m_xChildMapper( new OWrappedAccessibleChildrenManager( getComponentContext() ) ) + { + // determine if we're allowed to cache children + Reference< XAccessibleStateSet > xStates( m_xInnerContext->getAccessibleStateSet( ) ); + OSL_ENSURE( xStates.is(), "OAccessibleContextWrapperHelper::OAccessibleContextWrapperHelper: no inner state set!" ); + m_xChildMapper->setTransientChildren( !xStates.is() || xStates->contains( AccessibleStateType::MANAGES_DESCENDANTS) ); + + m_xChildMapper->setOwningAccessible( m_xOwningAccessible ); + } + + + void OAccessibleContextWrapperHelper::aggregateProxy( oslInterlockedCount& _rRefCount, ::cppu::OWeakObject& _rDelegator ) + { + Reference< XComponent > xInnerComponent( m_xInnerContext, UNO_QUERY ); + OSL_ENSURE( xInnerComponent.is(), "OComponentProxyAggregation::aggregateProxy: accessible is no XComponent!" ); + if ( xInnerComponent.is() ) + componentAggregateProxyFor( xInnerComponent, _rRefCount, _rDelegator ); + + // add as event listener to the inner context, because we want to multiplex the AccessibleEvents + osl_atomic_increment( &_rRefCount ); + { + Reference< XAccessibleEventBroadcaster > xBroadcaster( m_xInner, UNO_QUERY ); + if ( xBroadcaster.is() ) + xBroadcaster->addAccessibleEventListener( this ); + } + osl_atomic_decrement( &_rRefCount ); + } + + + OAccessibleContextWrapperHelper::~OAccessibleContextWrapperHelper( ) + { + OSL_ENSURE( m_rBHelper.bDisposed, "OAccessibleContextWrapperHelper::~OAccessibleContextWrapperHelper: you should ensure (in your dtor) that the object is disposed!" ); + } + + + Any SAL_CALL OAccessibleContextWrapperHelper::queryInterface( const Type& _rType ) + { + Any aReturn = OComponentProxyAggregationHelper::queryInterface( _rType ); + if ( !aReturn.hasValue() ) + aReturn = OAccessibleContextWrapperHelper_Base::queryInterface( _rType ); + return aReturn; + } + + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OAccessibleContextWrapperHelper, OComponentProxyAggregationHelper, OAccessibleContextWrapperHelper_Base ) + + + sal_Int32 OAccessibleContextWrapperHelper::baseGetAccessibleChildCount( ) + { + return m_xInnerContext->getAccessibleChildCount(); + } + + + Reference< XAccessible > OAccessibleContextWrapperHelper::baseGetAccessibleChild( sal_Int32 i ) + { + // get the child of the wrapped component + Reference< XAccessible > xInnerChild = m_xInnerContext->getAccessibleChild( i ); + return m_xChildMapper->getAccessibleWrapperFor( xInnerChild ); + } + + + Reference< XAccessibleRelationSet > OAccessibleContextWrapperHelper::baseGetAccessibleRelationSet( ) + { + return m_xInnerContext->getAccessibleRelationSet(); + // TODO: if this relation set would contain relations to siblings, we would normally need + // to wrap them, too... + } + + + void SAL_CALL OAccessibleContextWrapperHelper::notifyEvent( const AccessibleEventObject& _rEvent ) + { +#if OSL_DEBUG_LEVEL > 0 + if ( AccessibleEventId::STATE_CHANGED == _rEvent.EventId ) + { + bool bChildTransienceChanged = false; + sal_Int16 nChangeState = 0; + if ( _rEvent.OldValue >>= nChangeState ) + bChildTransienceChanged = bChildTransienceChanged || AccessibleStateType::MANAGES_DESCENDANTS == nChangeState; + if ( _rEvent.NewValue >>= nChangeState ) + bChildTransienceChanged = bChildTransienceChanged || AccessibleStateType::MANAGES_DESCENDANTS == nChangeState; + OSL_ENSURE( !bChildTransienceChanged, "OAccessibleContextWrapperHelper::notifyEvent: MANAGES_DESCENDANTS is not expected to change during runtime!" ); + // if this asserts, then we would need to update our m_bTransientChildren flag here, + // as well as (potentially) our child cache + } +#endif + AccessibleEventObject aTranslatedEvent( _rEvent ); + + { + ::osl::MutexGuard aGuard( m_rBHelper.rMutex ); + + // translate the event + queryInterface( cppu::UnoType<XInterface>::get() ) >>= aTranslatedEvent.Source; + m_xChildMapper->translateAccessibleEvent( _rEvent, aTranslatedEvent ); + + // see if any of these notifications affect our child manager + m_xChildMapper->handleChildNotification( _rEvent ); + + if ( aTranslatedEvent.NewValue == m_xInner ) + aTranslatedEvent.NewValue <<= aTranslatedEvent.Source; + if ( aTranslatedEvent.OldValue == m_xInner ) + aTranslatedEvent.OldValue <<= aTranslatedEvent.Source; + } + + notifyTranslatedEvent( aTranslatedEvent ); + } + + + void SAL_CALL OAccessibleContextWrapperHelper::dispose() + { + ::osl::MutexGuard aGuard( m_rBHelper.rMutex ); + + // stop multiplexing events + Reference< XAccessibleEventBroadcaster > xBroadcaster( m_xInner, UNO_QUERY ); + OSL_ENSURE( xBroadcaster.is(), "OAccessibleContextWrapperHelper::disposing(): inner context is no broadcaster!" ); + if ( xBroadcaster.is() ) + xBroadcaster->removeAccessibleEventListener( this ); + + // dispose the child cache/map + m_xChildMapper->dispose(); + + // let the base class dispose the inner component + OComponentProxyAggregationHelper::dispose(); + } + + + void SAL_CALL OAccessibleContextWrapperHelper::disposing( const EventObject& _rEvent ) + { + // simply disambiguate this + OComponentProxyAggregationHelper::disposing( _rEvent ); + } + + IMPLEMENT_FORWARD_XINTERFACE2( OAccessibleContextWrapper, OAccessibleContextWrapper_CBase, OAccessibleContextWrapperHelper ) + + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OAccessibleContextWrapper, OAccessibleContextWrapper_CBase, OAccessibleContextWrapperHelper ) + + + OAccessibleContextWrapper::OAccessibleContextWrapper( const Reference< XComponentContext >& _rxContext, + const Reference< XAccessibleContext >& _rxInnerAccessibleContext, const Reference< XAccessible >& _rxOwningAccessible, + const Reference< XAccessible >& _rxParentAccessible ) + :OAccessibleContextWrapper_CBase( m_aMutex ) + ,OAccessibleContextWrapperHelper( _rxContext, rBHelper, _rxInnerAccessibleContext, _rxOwningAccessible, _rxParentAccessible ) + ,m_nNotifierClient( 0 ) + { + aggregateProxy( m_refCount, *this ); + } + + + OAccessibleContextWrapper::~OAccessibleContextWrapper() + { + } + + + sal_Int32 SAL_CALL OAccessibleContextWrapper::getAccessibleChildCount( ) + { + return baseGetAccessibleChildCount(); + } + + + Reference< XAccessible > SAL_CALL OAccessibleContextWrapper::getAccessibleChild( sal_Int32 i ) + { + return baseGetAccessibleChild( i ); + } + + + Reference< XAccessible > SAL_CALL OAccessibleContextWrapper::getAccessibleParent( ) + { + return m_xParentAccessible; + } + + + sal_Int32 SAL_CALL OAccessibleContextWrapper::getAccessibleIndexInParent( ) + { + return m_xInnerContext->getAccessibleIndexInParent(); + } + + + sal_Int16 SAL_CALL OAccessibleContextWrapper::getAccessibleRole( ) + { + return m_xInnerContext->getAccessibleRole(); + } + + + OUString SAL_CALL OAccessibleContextWrapper::getAccessibleDescription( ) + { + return m_xInnerContext->getAccessibleDescription(); + } + + + OUString SAL_CALL OAccessibleContextWrapper::getAccessibleName( ) + { + return m_xInnerContext->getAccessibleName(); + } + + + Reference< XAccessibleRelationSet > SAL_CALL OAccessibleContextWrapper::getAccessibleRelationSet( ) + { + return baseGetAccessibleRelationSet(); + } + + + Reference< XAccessibleStateSet > SAL_CALL OAccessibleContextWrapper::getAccessibleStateSet( ) + { + return m_xInnerContext->getAccessibleStateSet(); + } + + + Locale SAL_CALL OAccessibleContextWrapper::getLocale( ) + { + return m_xInnerContext->getLocale(); + } + + + void OAccessibleContextWrapper::notifyTranslatedEvent( const AccessibleEventObject& _rEvent ) + { + if ( m_nNotifierClient ) + AccessibleEventNotifier::addEvent( m_nNotifierClient, _rEvent ); + } + + + void SAL_CALL OAccessibleContextWrapper::addAccessibleEventListener( const Reference< XAccessibleEventListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !m_nNotifierClient ) + m_nNotifierClient = AccessibleEventNotifier::registerClient( ); + AccessibleEventNotifier::addEventListener( m_nNotifierClient, _rxListener ); + } + + + void SAL_CALL OAccessibleContextWrapper::removeAccessibleEventListener( const Reference< XAccessibleEventListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( m_nNotifierClient ) + { + if ( 0 == AccessibleEventNotifier::removeEventListener( m_nNotifierClient, _rxListener ) ) + { + AccessibleEventNotifier::TClientId nId( m_nNotifierClient ); + m_nNotifierClient = 0; + AccessibleEventNotifier::revokeClient( nId ); + } + } + } + + + void SAL_CALL OAccessibleContextWrapper::disposing() + { + AccessibleEventNotifier::TClientId nClientId( 0 ); + + // --- <mutex lock> ----------------------------------------- + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // prepare notifying our AccessibleListeners + if ( m_nNotifierClient ) + { + nClientId = m_nNotifierClient; + m_nNotifierClient = 0; + } + } + // --- </mutex lock> ----------------------------------------- + + // let the base class do + OAccessibleContextWrapperHelper::dispose(); + + // notify the disposal + if ( nClientId ) + AccessibleEventNotifier::revokeClientNotifyDisposing( nClientId, *this ); + } +} // namespace accessibility + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/accimplaccess.cxx b/comphelper/source/misc/accimplaccess.cxx new file mode 100644 index 000000000..bcf05c926 --- /dev/null +++ b/comphelper/source/misc/accimplaccess.cxx @@ -0,0 +1,54 @@ +/* -*- 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/accimplaccess.hxx> +#include <comphelper/servicehelper.hxx> + + +namespace comphelper +{ + + + using ::com::sun::star::uno::Sequence; + + OAccessibleImplementationAccess::OAccessibleImplementationAccess( ) + { + } + + + OAccessibleImplementationAccess::~OAccessibleImplementationAccess( ) + { + } + + const Sequence<sal_Int8> & OAccessibleImplementationAccess::getUnoTunnelId() + { + static const comphelper::UnoIdInit implID; + return implID.getSeq(); + } + + + sal_Int64 SAL_CALL OAccessibleImplementationAccess::getSomething( const Sequence< sal_Int8 >& _rIdentifier ) + { + return comphelper::getSomethingImpl(_rIdentifier, this); + } + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/anycompare.cxx b/comphelper/source/misc/anycompare.cxx new file mode 100644 index 000000000..8a2387723 --- /dev/null +++ b/comphelper/source/misc/anycompare.cxx @@ -0,0 +1,453 @@ +/* -*- 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 <memory> +#include <optional> +#include <comphelper/anycompare.hxx> +#include <typelib/typedescription.hxx> + +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/Time.hpp> +#include <com/sun/star/util/DateTime.hpp> + +#include "typedescriptionref.hxx" + +namespace comphelper +{ + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::Type; + using ::com::sun::star::uno::TypeDescription; + using ::com::sun::star::uno::TypeClass_CHAR; + using ::com::sun::star::uno::TypeClass_BOOLEAN; + using ::com::sun::star::uno::TypeClass_BYTE; + using ::com::sun::star::uno::TypeClass_SHORT; + using ::com::sun::star::uno::TypeClass_UNSIGNED_SHORT; + using ::com::sun::star::uno::TypeClass_LONG; + using ::com::sun::star::uno::TypeClass_UNSIGNED_LONG; + using ::com::sun::star::uno::TypeClass_HYPER; + using ::com::sun::star::uno::TypeClass_UNSIGNED_HYPER; + using ::com::sun::star::uno::TypeClass_FLOAT; + using ::com::sun::star::uno::TypeClass_DOUBLE; + using ::com::sun::star::uno::TypeClass_STRING; + using ::com::sun::star::uno::TypeClass_TYPE; + using ::com::sun::star::uno::TypeClass_ENUM; + using ::com::sun::star::uno::TypeClass_INTERFACE; + using ::com::sun::star::uno::TypeClass_STRUCT; + using ::com::sun::star::i18n::XCollator; + using ::com::sun::star::util::Date; + using ::com::sun::star::util::Time; + using ::com::sun::star::util::DateTime; + using ::comphelper::detail::TypeDescriptionRef; + + namespace { + + class DatePredicateLess : public IKeyPredicateLess + { + public: + virtual bool isLess( css::uno::Any const & _lhs, css::uno::Any const & _rhs ) const override + { + Date lhs, rhs; + if ( !( _lhs >>= lhs ) + || !( _rhs >>= rhs ) + ) + throw css::lang::IllegalArgumentException("bad ordering", css::uno::Reference<css::uno::XInterface>(), -1); + // FIXME Timezone? + + if ( lhs.Year < rhs.Year ) + return true; + if ( lhs.Year > rhs.Year ) + return false; + + if ( lhs.Month < rhs.Month ) + return true; + if ( lhs.Month > rhs.Month ) + return false; + + if ( lhs.Day < rhs.Day ) + return true; + return false; + } + }; + + class TimePredicateLess : public IKeyPredicateLess + { + public: + virtual bool isLess( css::uno::Any const & _lhs, css::uno::Any const & _rhs ) const override + { + Time lhs, rhs; + if ( !( _lhs >>= lhs ) + || !( _rhs >>= rhs ) + ) + throw css::lang::IllegalArgumentException("bad ordering", css::uno::Reference<css::uno::XInterface>(), -1); + // FIXME Timezone? + + if ( lhs.Hours < rhs.Hours ) + return true; + if ( lhs.Hours > rhs.Hours ) + return false; + + if ( lhs.Minutes < rhs.Minutes ) + return true; + if ( lhs.Minutes > rhs.Minutes ) + return false; + + if ( lhs.Seconds < rhs.Seconds ) + return true; + if ( lhs.Seconds > rhs.Seconds ) + return false; + + if ( lhs.NanoSeconds < rhs.NanoSeconds ) + return true; + return false; + } + }; + + class DateTimePredicateLess : public IKeyPredicateLess + { + public: + virtual bool isLess( css::uno::Any const & _lhs, css::uno::Any const & _rhs ) const override + { + DateTime lhs, rhs; + if ( !( _lhs >>= lhs ) + || !( _rhs >>= rhs ) + ) + throw css::lang::IllegalArgumentException("bad ordering", css::uno::Reference<css::uno::XInterface>(), -1); + // FIXME Timezone? + + if ( lhs.Year < rhs.Year ) + return true; + if ( lhs.Year > rhs.Year ) + return false; + + if ( lhs.Month < rhs.Month ) + return true; + if ( lhs.Month > rhs.Month ) + return false; + + if ( lhs.Day < rhs.Day ) + return true; + if ( lhs.Day > rhs.Day ) + return false; + + if ( lhs.Hours < rhs.Hours ) + return true; + if ( lhs.Hours > rhs.Hours ) + return false; + + if ( lhs.Minutes < rhs.Minutes ) + return true; + if ( lhs.Minutes > rhs.Minutes ) + return false; + + if ( lhs.Seconds < rhs.Seconds ) + return true; + if ( lhs.Seconds > rhs.Seconds ) + return false; + + if ( lhs.NanoSeconds < rhs.NanoSeconds ) + return true; + return false; + } + }; + + bool anyLess( void const * lhs, typelib_TypeDescriptionReference * lhsType, + void const * rhs, typelib_TypeDescriptionReference * rhsType ); + + // For compound types we need to compare them member by member until we've + // checked them all or found a member that differs. For inequality checks + // we need to call anyLess() twice in both directions, this function does that. + std::optional<bool> anyCompare( void const * lhs, typelib_TypeDescriptionReference * lhsType, + void const * rhs, typelib_TypeDescriptionReference * rhsType ) + { + if( anyLess( lhs, lhsType, rhs, rhsType )) + return std::optional( true ); + if( anyLess( rhs, rhsType, lhs, lhsType )) + return std::optional( false ); + return std::nullopt; // equal, so can't yet tell if anyLess() should return + } + + // This is typelib_typedescription_equals(), but returns -1/0/1 values like strcmp(). + int compareTypes( const typelib_TypeDescription * lhsType, + const typelib_TypeDescription * rhsType ) + { + if( lhsType == rhsType ) + return 0; + if( lhsType->eTypeClass != rhsType->eTypeClass ) + return lhsType->eTypeClass - rhsType->eTypeClass; + if( lhsType->pTypeName->length != rhsType->pTypeName->length ) + return lhsType->pTypeName->length - rhsType->pTypeName->length; + return rtl_ustr_compare( lhsType->pTypeName->buffer, rhsType->pTypeName->buffer ); + } + + bool anyLess( void const * lhs, typelib_TypeDescriptionReference * lhsType, + void const * rhs, typelib_TypeDescriptionReference * rhsType ) + { + if (lhsType->eTypeClass != rhsType->eTypeClass) + return lhsType->eTypeClass < rhsType->eTypeClass; + + if (lhsType->eTypeClass == typelib_TypeClass_VOID) { + return false; + } + assert(lhs != nullptr); + assert(rhs != nullptr); + + switch (lhsType->eTypeClass) { + case typelib_TypeClass_INTERFACE: + return lhs < rhs; + case typelib_TypeClass_STRUCT: + case typelib_TypeClass_EXCEPTION: { + TypeDescription lhsTypeDescr( lhsType ); + if (!lhsTypeDescr.is()) + lhsTypeDescr.makeComplete(); + if (!lhsTypeDescr.is()) + throw css::lang::IllegalArgumentException("bad ordering", css::uno::Reference<css::uno::XInterface>(), -1); + TypeDescription rhsTypeDescr( rhsType ); + if (!rhsTypeDescr.is()) + rhsTypeDescr.makeComplete(); + if (!rhsTypeDescr.is()) + throw css::lang::IllegalArgumentException("bad ordering", css::uno::Reference<css::uno::XInterface>(), -1); + int compare = compareTypes( lhsTypeDescr.get(), rhsTypeDescr.get()); + if( compare != 0 ) + return compare < 0; + + typelib_CompoundTypeDescription * compType = + reinterpret_cast< typelib_CompoundTypeDescription * >( + lhsTypeDescr.get() ); + sal_Int32 nDescr = compType->nMembers; + + if (compType->pBaseTypeDescription) { + std::optional<bool> subLess = anyCompare( + lhs, reinterpret_cast< + typelib_TypeDescription * >( + compType->pBaseTypeDescription)->pWeakRef, + rhs, reinterpret_cast< + typelib_TypeDescription * >( + compType->pBaseTypeDescription)->pWeakRef); + if(subLess.has_value()) + return *subLess; + } + + typelib_TypeDescriptionReference ** ppTypeRefs = + compType->ppTypeRefs; + sal_Int32 * memberOffsets = compType->pMemberOffsets; + + for ( sal_Int32 nPos = 0; nPos < nDescr; ++nPos ) + { + TypeDescriptionRef memberType( ppTypeRefs[ nPos ] ); + if (!memberType.is()) + throw css::lang::IllegalArgumentException("bad ordering", css::uno::Reference<css::uno::XInterface>(), -1); + std::optional<bool> subLess = anyCompare( + static_cast< char const * >( + lhs ) + memberOffsets[ nPos ], + memberType->pWeakRef, + static_cast< char const * >( + rhs ) + memberOffsets[ nPos ], + memberType->pWeakRef); + if(subLess.has_value()) + return *subLess; + } + return false; // equal + } + case typelib_TypeClass_SEQUENCE: { + uno_Sequence * lhsSeq = *static_cast< uno_Sequence * const * >(lhs); + uno_Sequence * rhsSeq = *static_cast< uno_Sequence * const * >(rhs); + if( lhsSeq->nElements != rhsSeq->nElements) + return lhsSeq->nElements < rhsSeq->nElements; + sal_Int32 nElements = lhsSeq->nElements; + + TypeDescriptionRef lhsTypeDescr( lhsType ); + if (!lhsTypeDescr.is()) + throw css::lang::IllegalArgumentException("bad ordering", css::uno::Reference<css::uno::XInterface>(), -1); + TypeDescriptionRef rhsTypeDescr( rhsType ); + if (!rhsTypeDescr.is()) + throw css::lang::IllegalArgumentException("bad ordering", css::uno::Reference<css::uno::XInterface>(), -1); + int compare = compareTypes( lhsTypeDescr.get(), rhsTypeDescr.get()); + if( compare != 0 ) + return compare < 0; + + typelib_TypeDescriptionReference * elementTypeRef = + reinterpret_cast< typelib_IndirectTypeDescription * >(lhsTypeDescr.get())->pType; + TypeDescriptionRef elementTypeDescr( elementTypeRef ); + if (!elementTypeDescr.is()) + throw css::lang::IllegalArgumentException("bad ordering", css::uno::Reference<css::uno::XInterface>(), -1); + assert( elementTypeDescr.equals( TypeDescriptionRef( + reinterpret_cast< typelib_IndirectTypeDescription * >(lhsTypeDescr.get())->pType ))); + + sal_Int32 nElementSize = elementTypeDescr->nSize; + if (nElements > 0) + { + char const * lhsElements = lhsSeq->elements; + char const * rhsElements = rhsSeq->elements; + for ( sal_Int32 nPos = 0; nPos < nElements; ++nPos ) + { + std::optional<bool> subLess = anyCompare( + lhsElements + (nElementSize * nPos), + elementTypeDescr->pWeakRef, + rhsElements + (nElementSize * nPos), + elementTypeDescr->pWeakRef ); + if(subLess.has_value()) + return *subLess; + } + } + return false; // equal + } + case typelib_TypeClass_ANY: { + uno_Any const * lhsAny = static_cast< uno_Any const * >(lhs); + uno_Any const * rhsAny = static_cast< uno_Any const * >(rhs); + return anyLess( lhsAny->pData, lhsAny->pType, rhsAny->pData, rhsAny->pType ); + } + case typelib_TypeClass_TYPE: { + OUString const & lhsTypeName = OUString::unacquired( + &(*static_cast< typelib_TypeDescriptionReference * const * >(lhs))->pTypeName); + OUString const & rhsTypeName = OUString::unacquired( + &(*static_cast< typelib_TypeDescriptionReference * const * >(rhs))->pTypeName); + return lhsTypeName < rhsTypeName; + } + case typelib_TypeClass_STRING: { + OUString const & lhsStr = OUString::unacquired( + static_cast< rtl_uString * const * >(lhs) ); + OUString const & rhsStr = OUString::unacquired( + static_cast< rtl_uString * const * >(rhs) ); + return lhsStr < rhsStr; + } + case typelib_TypeClass_ENUM: { + TypeDescription lhsTypeDescr( lhsType ); + if (!lhsTypeDescr.is()) + lhsTypeDescr.makeComplete(); + if (!lhsTypeDescr.is()) + throw css::lang::IllegalArgumentException("bad ordering", css::uno::Reference<css::uno::XInterface>(), -1); + TypeDescription rhsTypeDescr( rhsType ); + if (!rhsTypeDescr.is()) + rhsTypeDescr.makeComplete(); + if (!rhsTypeDescr.is()) + throw css::lang::IllegalArgumentException("bad ordering", css::uno::Reference<css::uno::XInterface>(), -1); + int compare = compareTypes( lhsTypeDescr.get(), rhsTypeDescr.get()); + if( compare != 0 ) + return compare < 0; + + return *static_cast< int const * >(lhs) < *static_cast< int const * >(rhs); + } + case typelib_TypeClass_BOOLEAN: + return *static_cast< sal_Bool const * >(lhs) < *static_cast< sal_Bool const * >(rhs); + case typelib_TypeClass_CHAR: + return *static_cast< sal_Unicode const * >(lhs) < *static_cast< sal_Unicode const * >(rhs); + case typelib_TypeClass_FLOAT: + return *static_cast< float const * >(lhs) < *static_cast< float const * >(rhs); + case typelib_TypeClass_DOUBLE: + return *static_cast< double const * >(lhs) < *static_cast< double const * >(rhs); + case typelib_TypeClass_BYTE: + return *static_cast< sal_Int8 const * >(lhs) < *static_cast< sal_Int8 const * >(rhs); + case typelib_TypeClass_SHORT: + return *static_cast< sal_Int16 const * >(lhs) < *static_cast< sal_Int16 const * >(rhs); + case typelib_TypeClass_UNSIGNED_SHORT: + return *static_cast< sal_uInt16 const * >(lhs) < *static_cast< sal_uInt16 const * >(rhs); + case typelib_TypeClass_LONG: + return *static_cast< sal_Int32 const * >(lhs) < *static_cast< sal_Int32 const * >(rhs); + case typelib_TypeClass_UNSIGNED_LONG: + return *static_cast< sal_uInt32 const * >(lhs) < *static_cast< sal_uInt32 const * >(rhs); + case typelib_TypeClass_HYPER: + return *static_cast< sal_Int64 const * >(lhs) < *static_cast< sal_Int64 const * >(rhs); + case typelib_TypeClass_UNSIGNED_HYPER: + return *static_cast< sal_uInt64 const * >(lhs) < *static_cast< sal_uInt64 const * >(rhs); + // case typelib_TypeClass_UNKNOWN: + // case typelib_TypeClass_SERVICE: + // case typelib_TypeClass_MODULE: + default: + return false; + } + } + + } // namespace + + std::unique_ptr< IKeyPredicateLess > getStandardLessPredicate( Type const & i_type, Reference< XCollator > const & i_collator ) + { + std::unique_ptr< IKeyPredicateLess > pComparator; + switch ( i_type.getTypeClass() ) + { + case TypeClass_CHAR: + pComparator.reset( new ScalarPredicateLess< sal_Unicode > ); + break; + case TypeClass_BOOLEAN: + pComparator.reset( new ScalarPredicateLess< bool > ); + break; + case TypeClass_BYTE: + pComparator.reset( new ScalarPredicateLess< sal_Int8 > ); + break; + case TypeClass_SHORT: + pComparator.reset( new ScalarPredicateLess< sal_Int16 > ); + break; + case TypeClass_UNSIGNED_SHORT: + pComparator.reset( new ScalarPredicateLess< sal_uInt16 > ); + break; + case TypeClass_LONG: + pComparator.reset( new ScalarPredicateLess< sal_Int32 > ); + break; + case TypeClass_UNSIGNED_LONG: + pComparator.reset( new ScalarPredicateLess< sal_uInt32 > ); + break; + case TypeClass_HYPER: + pComparator.reset( new ScalarPredicateLess< sal_Int64 > ); + break; + case TypeClass_UNSIGNED_HYPER: + pComparator.reset( new ScalarPredicateLess< sal_uInt64 > ); + break; + case TypeClass_FLOAT: + pComparator.reset( new ScalarPredicateLess< float > ); + break; + case TypeClass_DOUBLE: + pComparator.reset( new ScalarPredicateLess< double > ); + break; + case TypeClass_STRING: + if ( i_collator.is() ) + pComparator.reset( new StringCollationPredicateLess( i_collator ) ); + else + pComparator.reset( new StringPredicateLess ); + break; + case TypeClass_TYPE: + pComparator.reset( new TypePredicateLess ); + break; + case TypeClass_ENUM: + pComparator.reset( new EnumPredicateLess( i_type ) ); + break; + case TypeClass_INTERFACE: + pComparator.reset( new InterfacePredicateLess ); + break; + case TypeClass_STRUCT: + if ( i_type.equals( ::cppu::UnoType< Date >::get() ) ) + pComparator.reset( new DatePredicateLess ); + else if ( i_type.equals( ::cppu::UnoType< Time >::get() ) ) + pComparator.reset( new TimePredicateLess ); + else if ( i_type.equals( ::cppu::UnoType< DateTime >::get() ) ) + pComparator.reset( new DateTimePredicateLess ); + break; + default: + break; + } + return pComparator; + } + + bool anyLess( css::uno::Any const & lhs, css::uno::Any const & rhs) + { + return anyLess( lhs.getValue(), lhs.getValueTypeRef(), rhs.getValue(), rhs.getValueTypeRef()); + } + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/anytohash.cxx b/comphelper/source/misc/anytohash.cxx new file mode 100644 index 000000000..4e97ea124 --- /dev/null +++ b/comphelper/source/misc/anytohash.cxx @@ -0,0 +1,210 @@ +/* -*- 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/anytohash.hxx> + +#include <o3tl/hash_combine.hxx> +#include <typelib/typedescription.hxx> + +#include <com/sun/star/uno/Sequence.hxx> + +#include "typedescriptionref.hxx" + +using namespace ::com::sun::star; +using ::com::sun::star::uno::TypeDescription; +using ::comphelper::detail::TypeDescriptionRef; + +namespace comphelper { +namespace { + +std::optional<size_t> hashValue( size_t hash, + void const * val, typelib_TypeDescriptionReference * typeRef ) +{ + o3tl::hash_combine( hash, typeRef->eTypeClass ); + if (typeRef->eTypeClass == typelib_TypeClass_VOID) { + return hash; + } + assert(val != nullptr); + + switch (typeRef->eTypeClass) { + case typelib_TypeClass_INTERFACE: { + return std::nullopt; // not implemented + } + case typelib_TypeClass_STRUCT: + case typelib_TypeClass_EXCEPTION: { + TypeDescription typeDescr( typeRef ); + if (!typeDescr.is()) + typeDescr.makeComplete(); + if (!typeDescr.is()) + return std::nullopt; + + typelib_CompoundTypeDescription * compType = + reinterpret_cast< typelib_CompoundTypeDescription * >( + typeDescr.get() ); + sal_Int32 nDescr = compType->nMembers; + + if (compType->pBaseTypeDescription) { + std::optional<size_t> tmpHash = hashValue( + hash, val, reinterpret_cast< + typelib_TypeDescription * >( + compType->pBaseTypeDescription)->pWeakRef); + if(!tmpHash.has_value()) + return std::nullopt; + hash = *tmpHash; + } + + typelib_TypeDescriptionReference ** ppTypeRefs = + compType->ppTypeRefs; + sal_Int32 * memberOffsets = compType->pMemberOffsets; + + for ( sal_Int32 nPos = 0; nPos < nDescr; ++nPos ) + { + TypeDescriptionRef memberType( ppTypeRefs[ nPos ] ); + if (!memberType.is()) + return std::nullopt; + + std::optional<size_t> tmpHash = hashValue( hash, + static_cast< char const * >( + val ) + memberOffsets[ nPos ], + memberType->pWeakRef ); + if(!tmpHash.has_value()) + return std::nullopt; + hash = *tmpHash; + } + break; + } + case typelib_TypeClass_SEQUENCE: { + TypeDescriptionRef typeDescr( typeRef ); + if (!typeDescr.is()) + return std::nullopt; + + typelib_TypeDescriptionReference * elementTypeRef = + reinterpret_cast< + typelib_IndirectTypeDescription * >(typeDescr.get())->pType; + TypeDescriptionRef elementTypeDescr( elementTypeRef ); + if (!elementTypeDescr.is()) + return std::nullopt; + + sal_Int32 nElementSize = elementTypeDescr->nSize; + uno_Sequence * seq = + *static_cast< uno_Sequence * const * >(val); + sal_Int32 nElements = seq->nElements; + + if (nElements > 0) + { + char const * pElements = seq->elements; + for ( sal_Int32 nPos = 0; nPos < nElements; ++nPos ) + { + std::optional<size_t> tmpHash = hashValue( hash, + pElements + (nElementSize * nPos), + elementTypeDescr->pWeakRef ); + if(!tmpHash.has_value()) + return std::nullopt; + hash = *tmpHash; + } + } + break; + } + case typelib_TypeClass_ANY: { + uno_Any const * pAny = static_cast< uno_Any const * >(val); + return hashValue( hash, pAny->pData, pAny->pType ); + } + case typelib_TypeClass_TYPE: { + OUString const & str = OUString::unacquired( + &(*static_cast< + typelib_TypeDescriptionReference * const * >(val) + )->pTypeName ); + o3tl::hash_combine( hash, str.hashCode() ); + break; + } + case typelib_TypeClass_STRING: { + OUString const & str = OUString::unacquired( + static_cast< rtl_uString * const * >(val) ); + o3tl::hash_combine( hash, str.hashCode() ); + break; + } + case typelib_TypeClass_ENUM: { + TypeDescription typeDescr( typeRef ); + if (!typeDescr.is()) + typeDescr.makeComplete(); + if (!typeDescr.is()) + return std::nullopt; + + o3tl::hash_combine( hash, *static_cast< int const * >(val)); + break; + } + case typelib_TypeClass_BOOLEAN: + if (*static_cast< sal_Bool const * >(val)) + o3tl::hash_combine( hash, true ); + else + o3tl::hash_combine( hash, false ); + break; + case typelib_TypeClass_CHAR: { + o3tl::hash_combine( hash, *static_cast< sal_Unicode const * >(val)); + break; + } + case typelib_TypeClass_FLOAT: + o3tl::hash_combine( hash, *static_cast< float const * >(val) ); + break; + case typelib_TypeClass_DOUBLE: + o3tl::hash_combine( hash, *static_cast< double const * >(val) ); + break; + case typelib_TypeClass_BYTE: + o3tl::hash_combine( hash, *static_cast< sal_Int8 const * >(val) ); + break; + case typelib_TypeClass_SHORT: + o3tl::hash_combine( hash, *static_cast< sal_Int16 const * >(val) ); + break; + case typelib_TypeClass_UNSIGNED_SHORT: + o3tl::hash_combine( hash, *static_cast< sal_uInt16 const * >(val) ); + break; + case typelib_TypeClass_LONG: + o3tl::hash_combine( hash, *static_cast< sal_Int32 const * >(val) ); + break; + case typelib_TypeClass_UNSIGNED_LONG: + o3tl::hash_combine( hash, *static_cast< sal_uInt32 const * >(val) ); + break; + case typelib_TypeClass_HYPER: + o3tl::hash_combine( hash, *static_cast< sal_Int64 const * >(val) ); + break; + case typelib_TypeClass_UNSIGNED_HYPER: + o3tl::hash_combine( hash, *static_cast< sal_uInt64 const * >(val) ); + break; +// case typelib_TypeClass_UNKNOWN: +// case typelib_TypeClass_SERVICE: +// case typelib_TypeClass_MODULE: + default: + return std::nullopt; + } + return hash; +} + +} // anon namespace + + +std::optional<size_t> anyToHash( uno::Any const & value ) +{ + size_t hash = 0; + return hashValue( hash, value.getValue(), value.getValueTypeRef()); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/anytostring.cxx b/comphelper/source/misc/anytostring.cxx new file mode 100644 index 000000000..ebc338b0b --- /dev/null +++ b/comphelper/source/misc/anytostring.cxx @@ -0,0 +1,316 @@ +/* -*- 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 <rtl/ustrbuf.hxx> +#include <typelib/typedescription.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include "typedescriptionref.hxx" + +using namespace ::com::sun::star; +using ::com::sun::star::uno::TypeDescription; +using ::comphelper::detail::TypeDescriptionRef; + +namespace comphelper { +namespace { + +void appendTypeError( + OUStringBuffer & buf, const typelib_TypeDescriptionReference * typeRef ) +{ + buf.append( "<cannot get type description of type " ); + buf.append( OUString::unacquired( &typeRef->pTypeName ) ); + buf.append( '>' ); +} + +void appendChar( OUStringBuffer & buf, sal_Unicode c ) +{ + if (c < ' ' || c > '~') { + buf.append( "\\X" ); + OUString const s( + OUString::number( static_cast< sal_Int32 >(c), 16 ) ); + for ( sal_Int32 f = 4 - s.getLength(); f > 0; --f ) + buf.append( '0' ); + buf.append( s ); + } + else { + buf.append( c ); + } +} + + +void appendValue( OUStringBuffer & buf, + void const * val, typelib_TypeDescriptionReference * typeRef, + bool prependType ) +{ + if (typeRef->eTypeClass == typelib_TypeClass_VOID) { + buf.append( "void" ); + return; + } + assert(val != nullptr); + + if (prependType && + typeRef->eTypeClass != typelib_TypeClass_STRING && + typeRef->eTypeClass != typelib_TypeClass_CHAR && + typeRef->eTypeClass != typelib_TypeClass_BOOLEAN) + { + buf.append( '(' ); + buf.append( OUString::unacquired( &typeRef->pTypeName ) ); + buf.append( ") " ); + } + + switch (typeRef->eTypeClass) { + case typelib_TypeClass_INTERFACE: { + buf.append( '@' ); + buf.append( reinterpret_cast< sal_Int64 >( + *static_cast< void * const * >(val) ), 16 ); + uno::Reference< lang::XServiceInfo > xServiceInfo( + *static_cast< uno::XInterface * const * >(val), + uno::UNO_QUERY ); + if (xServiceInfo.is()) { + buf.append( " (ImplementationName = \"" ); + buf.append( xServiceInfo->getImplementationName() ); + buf.append( "\")" ); + } + break; + } + case typelib_TypeClass_STRUCT: + case typelib_TypeClass_EXCEPTION: { + buf.append( "{ " ); + TypeDescription typeDescr( typeRef ); + if (!typeDescr.is()) + typeDescr.makeComplete(); + if (!typeDescr.is()) { + appendTypeError( buf, typeRef ); + } + else { + typelib_CompoundTypeDescription * compType = + reinterpret_cast< typelib_CompoundTypeDescription * >( + typeDescr.get() ); + sal_Int32 nDescr = compType->nMembers; + + if (compType->pBaseTypeDescription) { + appendValue( + buf, val, reinterpret_cast< + typelib_TypeDescription * >( + compType->pBaseTypeDescription)->pWeakRef, false ); + if (nDescr > 0) + buf.append( ", " ); + } + + typelib_TypeDescriptionReference ** ppTypeRefs = + compType->ppTypeRefs; + sal_Int32 * memberOffsets = compType->pMemberOffsets; + rtl_uString ** ppMemberNames = compType->ppMemberNames; + + for ( sal_Int32 nPos = 0; nPos < nDescr; ++nPos ) + { + buf.append( ppMemberNames[ nPos ] ); + buf.append( " = " ); + TypeDescriptionRef memberType( ppTypeRefs[ nPos ] ); + if (!memberType.is()) { + appendTypeError( buf, ppTypeRefs[ nPos ] ); + } + else { + appendValue( buf, + static_cast< char const * >( + val ) + memberOffsets[ nPos ], + memberType->pWeakRef, true ); + } + if (nPos < (nDescr - 1)) + buf.append( ", " ); + } + } + buf.append( " }" ); + break; + } + case typelib_TypeClass_SEQUENCE: { + TypeDescriptionRef typeDescr( typeRef ); + if (!typeDescr.is()) { + appendTypeError( buf,typeRef ); + } + else { + typelib_TypeDescriptionReference * elementTypeRef = + reinterpret_cast< + typelib_IndirectTypeDescription * >(typeDescr.get())->pType; + TypeDescriptionRef elementTypeDescr( elementTypeRef ); + if (!elementTypeDescr.is()) + { + appendTypeError( buf, elementTypeRef ); + } + else + { + sal_Int32 nElementSize = elementTypeDescr->nSize; + uno_Sequence * seq = + *static_cast< uno_Sequence * const * >(val); + sal_Int32 nElements = seq->nElements; + + if (nElements > 0) + { + buf.append( "{ " ); + char const * pElements = seq->elements; + for ( sal_Int32 nPos = 0; nPos < nElements; ++nPos ) + { + appendValue( + buf, pElements + (nElementSize * nPos), + elementTypeDescr->pWeakRef, false ); + if (nPos < (nElements - 1)) + buf.append( ", " ); + } + buf.append( " }" ); + } + else + { + buf.append( "{}" ); + } + } + } + break; + } + case typelib_TypeClass_ANY: { + buf.append( "{ " ); + uno_Any const * pAny = static_cast< uno_Any const * >(val); + appendValue( buf, pAny->pData, pAny->pType, true ); + buf.append( " }" ); + break; + } + case typelib_TypeClass_TYPE: + buf.append( (*static_cast< + typelib_TypeDescriptionReference * const * >(val) + )->pTypeName ); + break; + case typelib_TypeClass_STRING: { + buf.append( '\"' ); + OUString const & str = OUString::unacquired( + static_cast< rtl_uString * const * >(val) ); + sal_Int32 len = str.getLength(); + for ( sal_Int32 pos = 0; pos < len; ++pos ) + { + sal_Unicode c = str[ pos ]; + if (c == '\"') + buf.append( "\\\"" ); + else if (c == '\\') + buf.append( "\\\\" ); + else + appendChar( buf, c ); + } + buf.append( '\"' ); + break; + } + case typelib_TypeClass_ENUM: { + TypeDescription typeDescr( typeRef ); + if (!typeDescr.is()) + typeDescr.makeComplete(); + if (!typeDescr.is()) { + appendTypeError( buf, typeRef ); + } + else + { + sal_Int32 * pValues = + reinterpret_cast< typelib_EnumTypeDescription * >( + typeDescr.get() )->pEnumValues; + sal_Int32 nPos = reinterpret_cast< typelib_EnumTypeDescription * >( + typeDescr.get() )->nEnumValues; + while (nPos--) + { + if (pValues[ nPos ] == *static_cast< int const * >(val)) + break; + } + if (nPos >= 0) + { + buf.append( reinterpret_cast< typelib_EnumTypeDescription * >( + typeDescr.get() )->ppEnumNames[ nPos ] ); + } + else + { + buf.append( "?unknown enum value?" ); + } + } + break; + } + case typelib_TypeClass_BOOLEAN: + if (*static_cast< sal_Bool const * >(val)) + buf.append( "true" ); + else + buf.append( "false" ); + break; + case typelib_TypeClass_CHAR: { + buf.append( '\'' ); + sal_Unicode c = *static_cast< sal_Unicode const * >(val); + if (c == '\'') + buf.append( "\\\'" ); + else if (c == '\\') + buf.append( "\\\\" ); + else + appendChar( buf, c ); + buf.append( '\'' ); + break; + } + case typelib_TypeClass_FLOAT: + buf.append( *static_cast< float const * >(val) ); + break; + case typelib_TypeClass_DOUBLE: + buf.append( *static_cast< double const * >(val) ); + break; + case typelib_TypeClass_BYTE: + buf.append( static_cast< sal_Int32 >( + *static_cast< sal_Int8 const * >(val) ) ); + break; + case typelib_TypeClass_SHORT: + buf.append( static_cast< sal_Int32 >( + *static_cast< sal_Int16 const * >(val) ) ); + break; + case typelib_TypeClass_UNSIGNED_SHORT: + buf.append( static_cast< sal_Int32 >( + *static_cast< sal_uInt16 const * >(val) ) ); + break; + case typelib_TypeClass_LONG: + buf.append( *static_cast< sal_Int32 const * >(val) ); + break; + case typelib_TypeClass_UNSIGNED_LONG: + buf.append( static_cast< sal_Int64 >( + *static_cast< sal_uInt32 const * >(val) ) ); + break; + case typelib_TypeClass_HYPER: + case typelib_TypeClass_UNSIGNED_HYPER: + buf.append( *static_cast< sal_Int64 const * >(val) ); + break; +// case typelib_TypeClass_UNKNOWN: +// case typelib_TypeClass_SERVICE: +// case typelib_TypeClass_MODULE: + default: + buf.append( '?' ); + break; + } +} + +} // anon namespace + + +OUString anyToString( uno::Any const & value ) +{ + OUStringBuffer buf; + appendValue( buf, value.getValue(), value.getValueTypeRef(), true ); + return buf.makeStringAndClear(); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/asyncnotification.cxx b/comphelper/source/misc/asyncnotification.cxx new file mode 100644 index 000000000..cb8a2f251 --- /dev/null +++ b/comphelper/source/misc/asyncnotification.cxx @@ -0,0 +1,259 @@ +/* -*- 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/asyncnotification.hxx> +#include <comphelper/scopeguard.hxx> +#include <mutex> +#include <condition_variable> + +#include <cassert> +#include <stdexcept> +#include <vector> +#include <algorithm> + +namespace comphelper +{ + AnyEvent::AnyEvent() + { + } + + AnyEvent::~AnyEvent() + { + } + + namespace { + + struct ProcessableEvent + { + AnyEventRef aEvent; + ::rtl::Reference< IEventProcessor > xProcessor; + }; + + struct EqualProcessor + { + const ::rtl::Reference< IEventProcessor >& rProcessor; + explicit EqualProcessor( const ::rtl::Reference< IEventProcessor >& _rProcessor ) :rProcessor( _rProcessor ) { } + + bool operator()( const ProcessableEvent& _rEvent ) + { + return _rEvent.xProcessor.get() == rProcessor.get(); + } + }; + + } + + struct EventNotifierImpl + { + std::mutex aMutex; + std::condition_variable aPendingActions; + std::vector< ProcessableEvent > aEvents; + bool bTerminate; + // only used for AsyncEventNotifierAutoJoin + char const* name; + std::shared_ptr<AsyncEventNotifierAutoJoin> pKeepThisAlive; + + EventNotifierImpl() + : bTerminate(false) + , name(nullptr) + { + } + }; + + AsyncEventNotifierBase::AsyncEventNotifierBase() + : m_xImpl(new EventNotifierImpl) + { + } + + + AsyncEventNotifierBase::~AsyncEventNotifierBase() + { + } + + + void AsyncEventNotifierBase::removeEventsForProcessor( const ::rtl::Reference< IEventProcessor >& _xProcessor ) + { + std::scoped_lock aGuard( m_xImpl->aMutex ); + + // remove all events for this processor + m_xImpl->aEvents.erase(std::remove_if( m_xImpl->aEvents.begin(), m_xImpl->aEvents.end(), EqualProcessor( _xProcessor ) ), m_xImpl->aEvents.end()); + } + + + void SAL_CALL AsyncEventNotifierBase::terminate() + { + std::scoped_lock aGuard( m_xImpl->aMutex ); + + // remember the termination request + m_xImpl->bTerminate = true; + + // awake the thread + m_xImpl->aPendingActions.notify_all(); + } + + + void AsyncEventNotifierBase::addEvent( const AnyEventRef& _rEvent, const ::rtl::Reference< IEventProcessor >& _xProcessor ) + { + std::scoped_lock aGuard( m_xImpl->aMutex ); + + // remember this event + m_xImpl->aEvents.emplace_back( ProcessableEvent {_rEvent, _xProcessor} ); + + // awake the thread + m_xImpl->aPendingActions.notify_all(); + } + + + void AsyncEventNotifierBase::execute() + { + for (;;) + { + std::vector< ProcessableEvent > aEvents; + { + std::unique_lock aGuard(m_xImpl->aMutex); + m_xImpl->aPendingActions.wait(aGuard, + [this] { return m_xImpl->bTerminate || !m_xImpl->aEvents.empty(); } ); + if (m_xImpl->bTerminate) + return; + else + std::swap(aEvents, m_xImpl->aEvents); + } + for (ProcessableEvent& rEvent : aEvents) + { + assert(rEvent.xProcessor.is()); + rEvent.xProcessor->processEvent(*rEvent.aEvent); + } + aEvents.clear(); + } + } + + AsyncEventNotifier::AsyncEventNotifier(char const* name) + : salhelper::Thread(name) + { + } + + AsyncEventNotifier::~AsyncEventNotifier() + { + } + + void AsyncEventNotifier::execute() + { + return AsyncEventNotifierBase::execute(); + } + + void AsyncEventNotifier::terminate() + { + return AsyncEventNotifierBase::terminate(); + } + + namespace { + + std::mutex& GetTheNotifiersMutex() + { + static std::mutex MUTEX; + return MUTEX; + } + + } + + static std::vector<std::weak_ptr<AsyncEventNotifierAutoJoin>> g_Notifiers; + + void JoinAsyncEventNotifiers() + { + std::vector<std::weak_ptr<AsyncEventNotifierAutoJoin>> notifiers; + { + std::scoped_lock g(GetTheNotifiersMutex()); + notifiers = g_Notifiers; + } + for (std::weak_ptr<AsyncEventNotifierAutoJoin> const& wNotifier : notifiers) + { + std::shared_ptr<AsyncEventNotifierAutoJoin> const pNotifier( + wNotifier.lock()); + if (pNotifier) + { + pNotifier->terminate(); + pNotifier->join(); + } + } + // note it's possible that g_Notifiers isn't empty now in case of leaks, + // particularly since the UNO service manager isn't disposed yet + } + + AsyncEventNotifierAutoJoin::AsyncEventNotifierAutoJoin(char const* name) + { + m_xImpl->name = name; + } + + AsyncEventNotifierAutoJoin::~AsyncEventNotifierAutoJoin() + { + std::scoped_lock g(GetTheNotifiersMutex()); + // note: this doesn't happen atomically with the refcount + // hence it's possible this deletes > 1 or 0 elements + g_Notifiers.erase( + std::remove_if(g_Notifiers.begin(), g_Notifiers.end(), + [](std::weak_ptr<AsyncEventNotifierAutoJoin> const& w) { + return w.expired(); + } ), + g_Notifiers.end()); + } + + std::shared_ptr<AsyncEventNotifierAutoJoin> + AsyncEventNotifierAutoJoin::newAsyncEventNotifierAutoJoin(char const* name) + { + std::shared_ptr<AsyncEventNotifierAutoJoin> const ret( + new AsyncEventNotifierAutoJoin(name)); + std::scoped_lock g(GetTheNotifiersMutex()); + g_Notifiers.push_back(ret); + return ret; + } + + void AsyncEventNotifierAutoJoin::terminate() + { + return AsyncEventNotifierBase::terminate(); + } + + void AsyncEventNotifierAutoJoin::launch(std::shared_ptr<AsyncEventNotifierAutoJoin> const& xThis) + { + // see salhelper::Thread::launch + xThis->m_xImpl->pKeepThisAlive = xThis; + comphelper::ScopeGuard g([&xThis] { xThis->m_xImpl->pKeepThisAlive.reset(); }); + if (!xThis->create()) { + throw std::runtime_error("osl::Thread::create failed"); + } + g.dismiss(); + } + + void AsyncEventNotifierAutoJoin::run() + { + // see salhelper::Thread::run + comphelper::ScopeGuard g([this] { onTerminated(); }); + setName(m_xImpl->name); + execute(); + g.dismiss(); + } + + void AsyncEventNotifierAutoJoin::onTerminated() + { + // try to delete "this" + m_xImpl->pKeepThisAlive.reset(); + } + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/asyncquithandler.cxx b/comphelper/source/misc/asyncquithandler.cxx new file mode 100644 index 000000000..a04534ec9 --- /dev/null +++ b/comphelper/source/misc/asyncquithandler.cxx @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/frame/Desktop.hpp> +#include <com/sun/star/frame/XDesktop2.hpp> +#include <com/sun/star/uno/Reference.hxx> + +#include <comphelper/asyncquithandler.hxx> +#include <comphelper/processfactory.hxx> + +AsyncQuitHandler::AsyncQuitHandler() {} + +AsyncQuitHandler& AsyncQuitHandler::instance() +{ + static AsyncQuitHandler aInst; + return aInst; +} + +void AsyncQuitHandler::QuitApplication() +{ + css::uno::Reference<css::frame::XDesktop2> xDesktop + = css::frame::Desktop::create(comphelper::getProcessComponentContext()); + xDesktop->terminate(); +} + +IMPL_STATIC_LINK_NOARG(AsyncQuitHandler, OnAsyncQuit, void*, void) { QuitApplication(); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/automationinvokedzone.cxx b/comphelper/source/misc/automationinvokedzone.cxx new file mode 100644 index 000000000..4a71c4a51 --- /dev/null +++ b/comphelper/source/misc/automationinvokedzone.cxx @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <cassert> + +#include <comphelper/automationinvokedzone.hxx> + +namespace comphelper::Automation +{ +thread_local static int nActiveount = 0; + +bool AutomationInvokedZone::isActive() { return nActiveount > 0; } + +AutomationInvokedZone::AutomationInvokedZone() +{ + assert(nActiveount < 1000); + nActiveount++; +} + +AutomationInvokedZone::~AutomationInvokedZone() +{ + assert(nActiveount > 0); + nActiveount--; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/backupfilehelper.cxx b/comphelper/source/misc/backupfilehelper.cxx new file mode 100644 index 000000000..e9b173a47 --- /dev/null +++ b/comphelper/source/misc/backupfilehelper.cxx @@ -0,0 +1,2504 @@ +/* -*- 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/. + */ + +#include <sal/config.h> +#include <rtl/ustring.hxx> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> +#include <osl/file.hxx> +#include <comphelper/backupfilehelper.hxx> +#include <comphelper/DirectoryHelper.hxx> +#include <rtl/crc.h> +#include <algorithm> +#include <deque> +#include <memory> +#include <string_view> +#include <utility> +#include <vector> +#include <zlib.h> + +#include <comphelper/processfactory.hxx> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/xml/dom/XDocumentBuilder.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <com/sun/star/xml/dom/XElement.hpp> +#include <com/sun/star/xml/dom/XNodeList.hpp> +#include <com/sun/star/xml/dom/XText.hpp> +#include <com/sun/star/xml/sax/XSAXSerializable.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/XWriter.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <cppuhelper/exc_hlp.hxx> + +using namespace comphelper; +using namespace css; +using namespace css::xml::dom; + +const sal_uInt32 BACKUP_FILE_HELPER_BLOCK_SIZE = 16384; + +namespace +{ + typedef std::shared_ptr< osl::File > FileSharedPtr; + + sal_uInt32 createCrc32(FileSharedPtr const & rCandidate, sal_uInt32 nOffset) + { + sal_uInt32 nCrc32(0); + + if (rCandidate && osl::File::E_None == rCandidate->open(osl_File_OpenFlag_Read)) + { + sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt64 nBytesTransfer(0); + sal_uInt64 nSize(0); + + rCandidate->getSize(nSize); + + // set offset in source file - should be zero due to crc32 should + // only be needed to be created for new entries, gets loaded with old + // ones + if (osl::File::E_None == rCandidate->setPos(osl_Pos_Absolut, sal_Int64(nOffset))) + { + while (nSize != 0) + { + const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE))); + + if (osl::File::E_None == rCandidate->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) && nBytesTransfer == nToTransfer) + { + // add to crc and reduce size + nCrc32 = rtl_crc32(nCrc32, static_cast<void*>(aArray), static_cast<sal_uInt32>(nBytesTransfer)); + nSize -= nToTransfer; + } + else + { + // error - reset to zero again + nSize = nCrc32 = 0; + } + } + } + + rCandidate->close(); + } + + return nCrc32; + } + + bool read_sal_uInt32(FileSharedPtr const & rFile, sal_uInt32& rTarget) + { + sal_uInt8 aArray[4]; + sal_uInt64 nBaseRead(0); + + // read rTarget + if (osl::File::E_None == rFile->read(static_cast<void*>(aArray), 4, nBaseRead) && 4 == nBaseRead) + { + rTarget = (sal_uInt32(aArray[0]) << 24) + (sal_uInt32(aArray[1]) << 16) + (sal_uInt32(aArray[2]) << 8) + sal_uInt32(aArray[3]); + return true; + } + + return false; + } + + bool write_sal_uInt32(oslFileHandle& rHandle, sal_uInt32 nSource) + { + sal_uInt8 aArray[4]; + sal_uInt64 nBaseWritten(0); + + // write nSource + aArray[0] = sal_uInt8((nSource & 0xff000000) >> 24); + aArray[1] = sal_uInt8((nSource & 0x00ff0000) >> 16); + aArray[2] = sal_uInt8((nSource & 0x0000ff00) >> 8); + aArray[3] = sal_uInt8(nSource & 0x000000ff); + + return osl_File_E_None == osl_writeFile(rHandle, static_cast<const void*>(aArray), 4, &nBaseWritten) && 4 == nBaseWritten; + } + + bool read_OString(FileSharedPtr const & rFile, OString& rTarget) + { + sal_uInt32 nLength(0); + + if (!read_sal_uInt32(rFile, nLength)) + { + return false; + } + + sal_uInt64 nPos; + if (osl::File::E_None != rFile->getPos(nPos)) + return false; + + sal_uInt64 nSize; + if (osl::File::E_None != rFile->getSize(nSize)) + return false; + + const auto nRemainingSize = nSize - nPos; + if (nLength > nRemainingSize) + return false; + + std::vector<char> aTarget(nLength); + sal_uInt64 nBaseRead(0); + + // read rTarget + if (osl::File::E_None == rFile->read(static_cast<void*>(aTarget.data()), nLength, nBaseRead) && nLength == nBaseRead) + { + rTarget = OString(aTarget.data(), static_cast<sal_Int32>(nBaseRead)); + return true; + } + + return false; + } + + bool write_OString(oslFileHandle& rHandle, const OString& rSource) + { + const sal_uInt32 nLength(rSource.getLength()); + + if (!write_sal_uInt32(rHandle, nLength)) + { + return false; + } + + sal_uInt64 nBaseWritten(0); + + return osl_File_E_None == osl_writeFile(rHandle, static_cast<const void*>(rSource.getStr()), nLength, &nBaseWritten) && nLength == nBaseWritten; + } + + OUString createFileURL( + std::u16string_view rURL, std::u16string_view rName, std::u16string_view rExt) + { + OUString aRetval; + + if (!rURL.empty() && !rName.empty()) + { + aRetval = OUString::Concat(rURL) + "/" + rName; + + if (!rExt.empty()) + { + aRetval += OUString::Concat(".") + rExt; + } + } + + return aRetval; + } + + OUString createPackURL(std::u16string_view rURL, std::u16string_view rName) + { + OUString aRetval; + + if (!rURL.empty() && !rName.empty()) + { + aRetval = OUString::Concat(rURL) + "/" + rName + ".pack"; + } + + return aRetval; + } +} + +namespace +{ + enum PackageRepository { USER, SHARED, BUNDLED }; + + class ExtensionInfoEntry + { + private: + OString maName; // extension name + PackageRepository maRepository; // user|shared|bundled + bool mbEnabled; // state + + public: + ExtensionInfoEntry() + : maRepository(USER), + mbEnabled(false) + { + } + + ExtensionInfoEntry(OString aName, bool bEnabled) + : maName(std::move(aName)), + maRepository(USER), + mbEnabled(bEnabled) + { + } + + ExtensionInfoEntry(const uno::Reference< deployment::XPackage >& rxPackage) + : maName(OUStringToOString(rxPackage->getName(), RTL_TEXTENCODING_ASCII_US)), + maRepository(USER), + mbEnabled(false) + { + // check maRepository + const OString aRepName(OUStringToOString(rxPackage->getRepositoryName(), RTL_TEXTENCODING_ASCII_US)); + + if (aRepName == "shared") + { + maRepository = SHARED; + } + else if (aRepName == "bundled") + { + maRepository = BUNDLED; + } + + // check mbEnabled + const beans::Optional< beans::Ambiguous< sal_Bool > > option( + rxPackage->isRegistered(uno::Reference< task::XAbortChannel >(), + uno::Reference< ucb::XCommandEnvironment >())); + + if (option.IsPresent) + { + ::beans::Ambiguous< sal_Bool > const& reg = option.Value; + + if (!reg.IsAmbiguous) + { + mbEnabled = reg.Value; + } + } + } + + bool isSameExtension(const ExtensionInfoEntry& rComp) const + { + return (maRepository == rComp.maRepository && maName == rComp.maName); + } + + bool operator<(const ExtensionInfoEntry& rComp) const + { + if (maRepository == rComp.maRepository) + { + if (maName == rComp.maName) + { + return mbEnabled < rComp.mbEnabled; + } + else + { + return 0 > maName.compareTo(rComp.maName); + } + } + else + { + return maRepository < rComp.maRepository; + } + } + + bool read_entry(FileSharedPtr const & rFile) + { + // read maName + if (!read_OString(rFile, maName)) + { + return false; + } + + // read maRepository + sal_uInt32 nState(0); + + if (read_sal_uInt32(rFile, nState)) + { + maRepository = static_cast< PackageRepository >(nState); + } + else + { + return false; + } + + // read mbEnabled + if (read_sal_uInt32(rFile, nState)) + { + mbEnabled = static_cast< bool >(nState); + } + else + { + return false; + } + + return true; + } + + bool write_entry(oslFileHandle& rHandle) const + { + // write maName; + if (!write_OString(rHandle, maName)) + { + return false; + } + + // write maRepository + sal_uInt32 nState(maRepository); + + if (!write_sal_uInt32(rHandle, nState)) + { + return false; + } + + // write mbEnabled + nState = static_cast< sal_uInt32 >(mbEnabled); + + return write_sal_uInt32(rHandle, nState); + } + + const OString& getName() const + { + return maName; + } + + bool isEnabled() const + { + return mbEnabled; + } + }; + + typedef std::vector< ExtensionInfoEntry > ExtensionInfoEntryVector; + + constexpr OUStringLiteral gaRegPath { u"/registry/com.sun.star.comp.deployment.bundle.PackageRegistryBackend/backenddb.xml" }; + + class ExtensionInfo + { + private: + ExtensionInfoEntryVector maEntries; + + public: + ExtensionInfo() + { + } + + const ExtensionInfoEntryVector& getExtensionInfoEntryVector() const + { + return maEntries; + } + + void reset() + { + // clear all data + maEntries.clear(); + } + + void createUsingXExtensionManager() + { + // clear all data + reset(); + + // create content from current extension configuration + uno::Sequence< uno::Sequence< uno::Reference< deployment::XPackage > > > xAllPackages; + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + uno::Reference< deployment::XExtensionManager > m_xExtensionManager = deployment::ExtensionManager::get(xContext); + + try + { + xAllPackages = m_xExtensionManager->getAllExtensions(uno::Reference< task::XAbortChannel >(), + uno::Reference< ucb::XCommandEnvironment >()); + } + catch (const deployment::DeploymentException &) + { + return; + } + catch (const ucb::CommandFailedException &) + { + return; + } + catch (const ucb::CommandAbortedException &) + { + return; + } + catch (const lang::IllegalArgumentException & e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( e.Message, + e.Context, anyEx ); + } + + for (const uno::Sequence< uno::Reference< deployment::XPackage > > & xPackageList : std::as_const(xAllPackages)) + { + for (const uno::Reference< deployment::XPackage > & xPackage : xPackageList) + { + if (xPackage.is()) + { + maEntries.emplace_back(xPackage); + } + } + } + + if (!maEntries.empty()) + { + // sort the list + std::sort(maEntries.begin(), maEntries.end()); + } + } + + private: + void visitNodesXMLRead(const uno::Reference< xml::dom::XElement >& rElement) + { + if (!rElement.is()) + return; + + const OUString aTagName(rElement->getTagName()); + + if (aTagName == "extension") + { + OUString aAttrUrl(rElement->getAttribute("url")); + const OUString aAttrRevoked(rElement->getAttribute("revoked")); + + if (!aAttrUrl.isEmpty()) + { + const sal_Int32 nIndex(aAttrUrl.lastIndexOf('/')); + + if (nIndex > 0 && aAttrUrl.getLength() > nIndex + 1) + { + aAttrUrl = aAttrUrl.copy(nIndex + 1); + } + + const bool bEnabled(aAttrRevoked.isEmpty() || !aAttrRevoked.toBoolean()); + maEntries.emplace_back( + OUStringToOString(aAttrUrl, RTL_TEXTENCODING_ASCII_US), + bEnabled); + } + } + else + { + uno::Reference< xml::dom::XNodeList > aList = rElement->getChildNodes(); + + if (aList.is()) + { + const sal_Int32 nLength(aList->getLength()); + + for (sal_Int32 a(0); a < nLength; a++) + { + const uno::Reference< xml::dom::XElement > aChild(aList->item(a), uno::UNO_QUERY); + + if (aChild.is()) + { + visitNodesXMLRead(aChild); + } + } + } + } + } + + public: + void createUserExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL) + { + const OUString aPath( + OUString::Concat(rUserConfigWorkURL) + "/uno_packages/cache" + gaRegPath); + createExtensionRegistryEntriesFromXML(aPath); + } + + void createSharedExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL) + { + const OUString aPath( + OUString::Concat(rUserConfigWorkURL) + "/extensions/shared" + gaRegPath); + createExtensionRegistryEntriesFromXML(aPath); + } + + void createBundledExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL) + { + const OUString aPath( + OUString::Concat(rUserConfigWorkURL) + "/extensions/bundled" + gaRegPath); + createExtensionRegistryEntriesFromXML(aPath); + } + + + void createExtensionRegistryEntriesFromXML(const OUString& aPath) + { + if (DirectoryHelper::fileExists(aPath)) + { + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + uno::Reference< xml::dom::XDocumentBuilder > xBuilder(xml::dom::DocumentBuilder::create(xContext)); + uno::Reference< xml::dom::XDocument > aDocument = xBuilder->parseURI(aPath); + + if (aDocument.is()) + { + visitNodesXMLRead(aDocument->getDocumentElement()); + } + } + + if (!maEntries.empty()) + { + // sort the list + std::sort(maEntries.begin(), maEntries.end()); + } + } + + private: + static bool visitNodesXMLChange( + const OUString& rTagToSearch, + const uno::Reference< xml::dom::XElement >& rElement, + const ExtensionInfoEntryVector& rToBeEnabled, + const ExtensionInfoEntryVector& rToBeDisabled) + { + bool bChanged(false); + + if (rElement.is()) + { + const OUString aTagName(rElement->getTagName()); + + if (aTagName == rTagToSearch) + { + const OString aAttrUrl(OUStringToOString(rElement->getAttribute("url"), RTL_TEXTENCODING_ASCII_US)); + const OUString aAttrRevoked(rElement->getAttribute("revoked")); + const bool bEnabled(aAttrRevoked.isEmpty() || !aAttrRevoked.toBoolean()); + + if (!aAttrUrl.isEmpty()) + { + for (const auto& enable : rToBeEnabled) + { + if (-1 != aAttrUrl.indexOf(enable.getName())) + { + if (!bEnabled) + { + // needs to be enabled + rElement->removeAttribute("revoked"); + bChanged = true; + } + } + } + + for (const auto& disable : rToBeDisabled) + { + if (-1 != aAttrUrl.indexOf(disable.getName())) + { + if (bEnabled) + { + // needs to be disabled + rElement->setAttribute("revoked", "true"); + bChanged = true; + } + } + } + } + } + else + { + uno::Reference< xml::dom::XNodeList > aList = rElement->getChildNodes(); + + if (aList.is()) + { + const sal_Int32 nLength(aList->getLength()); + + for (sal_Int32 a(0); a < nLength; a++) + { + const uno::Reference< xml::dom::XElement > aChild(aList->item(a), uno::UNO_QUERY); + + if (aChild.is()) + { + bChanged |= visitNodesXMLChange( + rTagToSearch, + aChild, + rToBeEnabled, + rToBeDisabled); + } + } + } + } + } + + return bChanged; + } + + static void visitNodesXMLChangeOneCase( + const OUString& rUnoPackagReg, + const OUString& rTagToSearch, + const ExtensionInfoEntryVector& rToBeEnabled, + const ExtensionInfoEntryVector& rToBeDisabled) + { + if (!DirectoryHelper::fileExists(rUnoPackagReg)) + return; + + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + uno::Reference< xml::dom::XDocumentBuilder > xBuilder = xml::dom::DocumentBuilder::create(xContext); + uno::Reference< xml::dom::XDocument > aDocument = xBuilder->parseURI(rUnoPackagReg); + + if (!aDocument.is()) + return; + + if (!visitNodesXMLChange( + rTagToSearch, + aDocument->getDocumentElement(), + rToBeEnabled, + rToBeDisabled)) + return; + + // did change - write back + uno::Reference< xml::sax::XSAXSerializable > xSerializer(aDocument, uno::UNO_QUERY); + + if (!xSerializer.is()) + return; + + // create a SAXWriter + uno::Reference< xml::sax::XWriter > const xSaxWriter = xml::sax::Writer::create(xContext); + uno::Reference< io::XTempFile > xTempFile = io::TempFile::create(xContext); + uno::Reference< io::XOutputStream > xOutStrm = xTempFile->getOutputStream(); + + // set output stream and do the serialization + xSaxWriter->setOutputStream(xOutStrm); + xSerializer->serialize(xSaxWriter, uno::Sequence< beans::StringPair >()); + + // get URL from temp file + OUString aTempURL = xTempFile->getUri(); + + // copy back file + if (aTempURL.isEmpty() || !DirectoryHelper::fileExists(aTempURL)) + return; + + if (DirectoryHelper::fileExists(rUnoPackagReg)) + { + osl::File::remove(rUnoPackagReg); + } + +#if OSL_DEBUG_LEVEL > 1 + SAL_WARN_IF(osl::FileBase::E_None != osl::File::move(aTempURL, rUnoPackagReg), "comphelper.backupfilehelper", "could not copy back modified Extension configuration file"); +#else + osl::File::move(aTempURL, rUnoPackagReg); +#endif + } + + public: + static void changeEnableDisableStateInXML( + std::u16string_view rUserConfigWorkURL, + const ExtensionInfoEntryVector& rToBeEnabled, + const ExtensionInfoEntryVector& rToBeDisabled) + { + static const OUStringLiteral aRegPathFront(u"/uno_packages/cache/registry/com.sun.star.comp.deployment."); + static const OUStringLiteral aRegPathBack(u".PackageRegistryBackend/backenddb.xml"); + // first appearance to check + { + const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "bundle" + aRegPathBack); + + visitNodesXMLChangeOneCase( + aUnoPackagReg, + "extension", + rToBeEnabled, + rToBeDisabled); + } + + // second appearance to check + { + const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "configuration" + aRegPathBack); + + visitNodesXMLChangeOneCase( + aUnoPackagReg, + "configuration", + rToBeEnabled, + rToBeDisabled); + } + + // third appearance to check + { + const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "script" + aRegPathBack); + + visitNodesXMLChangeOneCase( + aUnoPackagReg, + "script", + rToBeEnabled, + rToBeDisabled); + } + } + + bool read_entries(FileSharedPtr const & rFile) + { + // read NumExtensionEntries + sal_uInt32 nExtEntries(0); + + if (!read_sal_uInt32(rFile, nExtEntries)) + { + return false; + } + + // coverity#1373663 Untrusted loop bound, check file size + // isn't utterly broken + sal_uInt64 nFileSize(0); + rFile->getSize(nFileSize); + if (nFileSize < nExtEntries) + return false; + + for (sal_uInt32 a(0); a < nExtEntries; a++) + { + ExtensionInfoEntry aNewEntry; + + if (aNewEntry.read_entry(rFile)) + { + maEntries.push_back(aNewEntry); + } + else + { + return false; + } + } + + return true; + } + + bool write_entries(oslFileHandle& rHandle) const + { + const sal_uInt32 nExtEntries(maEntries.size()); + + if (!write_sal_uInt32(rHandle, nExtEntries)) + { + return false; + } + + for (const auto& a : maEntries) + { + if (!a.write_entry(rHandle)) + { + return false; + } + } + + return true; + } + + bool createTempFile(OUString& rTempFileName) + { + oslFileHandle aHandle; + bool bRetval(false); + + // create current configuration + if (maEntries.empty()) + { + createUsingXExtensionManager(); + } + + // open target temp file and write current configuration to it - it exists until deleted + if (osl::File::E_None == osl::FileBase::createTempFile(nullptr, &aHandle, &rTempFileName)) + { + bRetval = write_entries(aHandle); + + // close temp file - it exists until deleted + osl_closeFile(aHandle); + } + + return bRetval; + } + + bool areThereEnabledExtensions() const + { + for (const auto& a : maEntries) + { + if (a.isEnabled()) + { + return true; + } + } + + return false; + } + }; +} + +namespace +{ + class PackedFileEntry + { + private: + sal_uInt32 mnFullFileSize; // size in bytes of unpacked original file + sal_uInt32 mnPackFileSize; // size in bytes in file backup package (smaller if compressed, same if not) + sal_uInt32 mnOffset; // offset in File (zero identifies new file) + sal_uInt32 mnCrc32; // checksum + FileSharedPtr maFile; // file where to find the data (at offset) + bool const mbDoCompress; // flag if this file is scheduled to be compressed when written + + bool copy_content_straight(oslFileHandle& rTargetHandle) + { + if (!maFile || osl::File::E_None != maFile->open(osl_File_OpenFlag_Read)) + return false; + + sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt64 nBytesTransfer(0); + sal_uInt64 nSize(getPackFileSize()); + + // set offset in source file - when this is zero, a new file is to be added + if (osl::File::E_None == maFile->setPos(osl_Pos_Absolut, sal_Int64(getOffset()))) + { + while (nSize != 0) + { + const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE))); + + if (osl::File::E_None != maFile->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer) + { + break; + } + + if (osl_File_E_None != osl_writeFile(rTargetHandle, static_cast<const void*>(aArray), nToTransfer, &nBytesTransfer) || nBytesTransfer != nToTransfer) + { + break; + } + + nSize -= nToTransfer; + } + } + + maFile->close(); + return (0 == nSize); + } + + bool copy_content_compress(oslFileHandle& rTargetHandle) + { + if (!maFile || osl::File::E_None != maFile->open(osl_File_OpenFlag_Read)) + return false; + + sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt8 aBuffer[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt64 nBytesTransfer(0); + sal_uInt64 nSize(getPackFileSize()); + z_stream zstream; + memset(&zstream, 0, sizeof(zstream)); + + if (Z_OK == deflateInit(&zstream, Z_BEST_COMPRESSION)) + { + // set offset in source file - when this is zero, a new file is to be added + if (osl::File::E_None == maFile->setPos(osl_Pos_Absolut, sal_Int64(getOffset()))) + { + bool bOkay(true); + + while (bOkay && nSize != 0) + { + const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE))); + + if (osl::File::E_None != maFile->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer) + { + break; + } + + zstream.avail_in = nToTransfer; + zstream.next_in = reinterpret_cast<unsigned char*>(aArray); + + do { + zstream.avail_out = BACKUP_FILE_HELPER_BLOCK_SIZE; + zstream.next_out = reinterpret_cast<unsigned char*>(aBuffer); +#if !defined Z_PREFIX + const sal_Int64 nRetval(deflate(&zstream, nSize == nToTransfer ? Z_FINISH : Z_NO_FLUSH)); +#else + const sal_Int64 nRetval(z_deflate(&zstream, nSize == nToTransfer ? Z_FINISH : Z_NO_FLUSH)); +#endif + if (Z_STREAM_ERROR == nRetval) + { + bOkay = false; + } + else + { + const sal_uInt64 nAvailable(BACKUP_FILE_HELPER_BLOCK_SIZE - zstream.avail_out); + + if (osl_File_E_None != osl_writeFile(rTargetHandle, static_cast<const void*>(aBuffer), nAvailable, &nBytesTransfer) || nBytesTransfer != nAvailable) + { + bOkay = false; + } + } + } while (bOkay && 0 == zstream.avail_out); + + if (!bOkay) + { + break; + } + + nSize -= nToTransfer; + } + +#if !defined Z_PREFIX + deflateEnd(&zstream); +#else + z_deflateEnd(&zstream); +#endif + } + } + + maFile->close(); + + // get compressed size and add to entry + if (mnFullFileSize == mnPackFileSize && mnFullFileSize == zstream.total_in) + { + mnPackFileSize = zstream.total_out; + } + + return (0 == nSize); + } + + bool copy_content_uncompress(oslFileHandle& rTargetHandle) + { + if (!maFile || osl::File::E_None != maFile->open(osl_File_OpenFlag_Read)) + return false; + + sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt8 aBuffer[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt64 nBytesTransfer(0); + sal_uInt64 nSize(getPackFileSize()); + z_stream zstream; + memset(&zstream, 0, sizeof(zstream)); + + if (Z_OK == inflateInit(&zstream)) + { + // set offset in source file - when this is zero, a new file is to be added + if (osl::File::E_None == maFile->setPos(osl_Pos_Absolut, sal_Int64(getOffset()))) + { + bool bOkay(true); + + while (bOkay && nSize != 0) + { + const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE))); + + if (osl::File::E_None != maFile->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer) + { + break; + } + + zstream.avail_in = nToTransfer; + zstream.next_in = reinterpret_cast<unsigned char*>(aArray); + + do { + zstream.avail_out = BACKUP_FILE_HELPER_BLOCK_SIZE; + zstream.next_out = reinterpret_cast<unsigned char*>(aBuffer); +#if !defined Z_PREFIX + const sal_Int64 nRetval(inflate(&zstream, Z_NO_FLUSH)); +#else + const sal_Int64 nRetval(z_inflate(&zstream, Z_NO_FLUSH)); +#endif + if (Z_STREAM_ERROR == nRetval) + { + bOkay = false; + } + else + { + const sal_uInt64 nAvailable(BACKUP_FILE_HELPER_BLOCK_SIZE - zstream.avail_out); + + if (osl_File_E_None != osl_writeFile(rTargetHandle, static_cast<const void*>(aBuffer), nAvailable, &nBytesTransfer) || nBytesTransfer != nAvailable) + { + bOkay = false; + } + } + } while (bOkay && 0 == zstream.avail_out); + + if (!bOkay) + { + break; + } + + nSize -= nToTransfer; + } + +#if !defined Z_PREFIX + deflateEnd(&zstream); +#else + z_deflateEnd(&zstream); +#endif + } + } + + maFile->close(); + return (0 == nSize); + } + + + public: + // create new, uncompressed entry + PackedFileEntry( + sal_uInt32 nFullFileSize, + sal_uInt32 nCrc32, + FileSharedPtr xFile, + bool bDoCompress) + : mnFullFileSize(nFullFileSize), + mnPackFileSize(nFullFileSize), + mnOffset(0), + mnCrc32(nCrc32), + maFile(std::move(xFile)), + mbDoCompress(bDoCompress) + { + } + + // create entry to be loaded as header (read_header) + PackedFileEntry() + : mnFullFileSize(0), + mnPackFileSize(0), + mnOffset(0), + mnCrc32(0), + mbDoCompress(false) + { + } + + sal_uInt32 getFullFileSize() const + { + return mnFullFileSize; + } + + sal_uInt32 getPackFileSize() const + { + return mnPackFileSize; + } + + sal_uInt32 getOffset() const + { + return mnOffset; + } + + void setOffset(sal_uInt32 nOffset) + { + mnOffset = nOffset; + } + + static sal_uInt32 getEntrySize() + { + return 12; + } + + sal_uInt32 getCrc32() const + { + return mnCrc32; + } + + bool read_header(FileSharedPtr const & rFile) + { + if (!rFile) + { + return false; + } + + maFile = rFile; + + // read and compute full file size + if (!read_sal_uInt32(rFile, mnFullFileSize)) + { + return false; + } + + // read and compute entry crc32 + if (!read_sal_uInt32(rFile, mnCrc32)) + { + return false; + } + + // read and compute packed size + if (!read_sal_uInt32(rFile, mnPackFileSize)) + { + return false; + } + + return true; + } + + bool write_header(oslFileHandle& rHandle) const + { + // write full file size + if (!write_sal_uInt32(rHandle, mnFullFileSize)) + { + return false; + } + + // write crc32 + if (!write_sal_uInt32(rHandle, mnCrc32)) + { + return false; + } + + // write packed file size + if (!write_sal_uInt32(rHandle, mnPackFileSize)) + { + return false; + } + + return true; + } + + bool copy_content(oslFileHandle& rTargetHandle, bool bUncompress) + { + if (bUncompress) + { + if (getFullFileSize() == getPackFileSize()) + { + // not compressed, just copy + return copy_content_straight(rTargetHandle); + } + else + { + // compressed, need to uncompress on copy + return copy_content_uncompress(rTargetHandle); + } + } + else if (0 == getOffset()) + { + if (mbDoCompress) + { + // compressed wanted, need to compress on copy + return copy_content_compress(rTargetHandle); + } + else + { + // not compressed, straight copy + return copy_content_straight(rTargetHandle); + } + } + else + { + return copy_content_straight(rTargetHandle); + } + } + }; +} + +namespace +{ + class PackedFile + { + private: + const OUString maURL; + std::deque< PackedFileEntry > + maPackedFileEntryVector; + bool mbChanged; + + public: + PackedFile(const OUString& rURL) + : maURL(rURL), + mbChanged(false) + { + FileSharedPtr aSourceFile = std::make_shared<osl::File>(rURL); + + if (osl::File::E_None == aSourceFile->open(osl_File_OpenFlag_Read)) + { + sal_uInt64 nBaseLen(0); + aSourceFile->getSize(nBaseLen); + + // we need at least File_ID and num entries -> 8byte + if (8 < nBaseLen) + { + sal_uInt8 aArray[4]; + sal_uInt64 nBaseRead(0); + + // read and check File_ID + if (osl::File::E_None == aSourceFile->read(static_cast< void* >(aArray), 4, nBaseRead) && 4 == nBaseRead) + { + if ('P' == aArray[0] && 'A' == aArray[1] && 'C' == aArray[2] && 'K' == aArray[3]) + { + // read and compute num entries in this file + if (osl::File::E_None == aSourceFile->read(static_cast<void*>(aArray), 4, nBaseRead) && 4 == nBaseRead) + { + sal_uInt32 nEntries((sal_uInt32(aArray[0]) << 24) + (sal_uInt32(aArray[1]) << 16) + (sal_uInt32(aArray[2]) << 8) + sal_uInt32(aArray[3])); + + // if there are entries (and less than max), read them + if (nEntries >= 1 && nEntries <= 10) + { + for (sal_uInt32 a(0); a < nEntries; a++) + { + // create new entry, read header (size, crc and PackedSize), + // set offset and source file + PackedFileEntry aEntry; + + if (aEntry.read_header(aSourceFile)) + { + // add to local data + maPackedFileEntryVector.push_back(aEntry); + } + else + { + // error + nEntries = 0; + } + } + + if (0 == nEntries) + { + // on read error clear local data + maPackedFileEntryVector.clear(); + } + else + { + // calculate and set offsets to file binary content + sal_uInt32 nHeaderSize(8); + + nHeaderSize += maPackedFileEntryVector.size() * PackedFileEntry::getEntrySize(); + + sal_uInt32 nOffset(nHeaderSize); + + for (auto& b : maPackedFileEntryVector) + { + b.setOffset(nOffset); + nOffset += b.getPackFileSize(); + } + } + } + } + } + } + } + + aSourceFile->close(); + } + + if (maPackedFileEntryVector.empty()) + { + // on error or no data get rid of pack file + osl::File::remove(maURL); + } + } + + void flush() + { + bool bRetval(true); + + if (maPackedFileEntryVector.empty()) + { + // get rid of (now?) empty pack file + osl::File::remove(maURL); + } + else if (mbChanged) + { + // need to create a new pack file, do this in a temp file to which data + // will be copied from local file (so keep it here until this is done) + oslFileHandle aHandle = nullptr; + OUString aTempURL; + + // open target temp file - it exists until deleted + if (osl::File::E_None == osl::FileBase::createTempFile(nullptr, &aHandle, &aTempURL)) + { + sal_uInt8 aArray[4]; + sal_uInt64 nBaseWritten(0); + + aArray[0] = 'P'; + aArray[1] = 'A'; + aArray[2] = 'C'; + aArray[3] = 'K'; + + // write File_ID + if (osl_File_E_None == osl_writeFile(aHandle, static_cast<const void*>(aArray), 4, &nBaseWritten) && 4 == nBaseWritten) + { + const sal_uInt32 nSize(maPackedFileEntryVector.size()); + + // write number of entries + if (write_sal_uInt32(aHandle, nSize)) + { + // write placeholder for headers. Due to the fact that + // PackFileSize for newly added files gets set during + // writing the content entry, write headers after content + // is written. To do so, write placeholders here + sal_uInt32 nWriteSize(0); + + nWriteSize += maPackedFileEntryVector.size() * PackedFileEntry::getEntrySize(); + + aArray[0] = aArray[1] = aArray[2] = aArray[3] = 0; + + for (sal_uInt32 a(0); bRetval && a < nWriteSize; a++) + { + if (osl_File_E_None != osl_writeFile(aHandle, static_cast<const void*>(aArray), 1, &nBaseWritten) || 1 != nBaseWritten) + { + bRetval = false; + } + } + + if (bRetval) + { + // write contents - this may adapt PackFileSize for new + // files + for (auto& candidate : maPackedFileEntryVector) + { + if (!candidate.copy_content(aHandle, false)) + { + bRetval = false; + break; + } + } + } + + if (bRetval) + { + // seek back to header start (at position 8) + if (osl_File_E_None != osl_setFilePos(aHandle, osl_Pos_Absolut, sal_Int64(8))) + { + bRetval = false; + } + } + + if (bRetval) + { + // write headers + for (const auto& candidate : maPackedFileEntryVector) + { + if (!candidate.write_header(aHandle)) + { + // error + bRetval = false; + break; + } + } + } + } + } + } + + // close temp file (in all cases) - it exists until deleted + osl_closeFile(aHandle); + + if (bRetval) + { + // copy over existing file by first deleting original + // and moving the temp file to old original + osl::File::remove(maURL); + osl::File::move(aTempURL, maURL); + } + + // delete temp file (in all cases - it may be moved already) + osl::File::remove(aTempURL); + } + } + + bool tryPush(FileSharedPtr const & rFileCandidate, bool bCompress) + { + sal_uInt64 nFileSize(0); + + if (rFileCandidate && osl::File::E_None == rFileCandidate->open(osl_File_OpenFlag_Read)) + { + rFileCandidate->getSize(nFileSize); + rFileCandidate->close(); + } + + if (0 == nFileSize) + { + // empty file offered + return false; + } + + bool bNeedToAdd(false); + sal_uInt32 nCrc32(0); + + if (maPackedFileEntryVector.empty()) + { + // no backup yet, add as 1st backup + bNeedToAdd = true; + } + else + { + // already backups there, check if different from last entry + const PackedFileEntry& aLastEntry = maPackedFileEntryVector.back(); + + // check if file is different + if (aLastEntry.getFullFileSize() != static_cast<sal_uInt32>(nFileSize)) + { + // different size, different file + bNeedToAdd = true; + } + else + { + // same size, check crc32 + nCrc32 = createCrc32(rFileCandidate, 0); + + if (nCrc32 != aLastEntry.getCrc32()) + { + // different crc, different file + bNeedToAdd = true; + } + } + } + + if (bNeedToAdd) + { + // create crc32 if not yet done + if (0 == nCrc32) + { + nCrc32 = createCrc32(rFileCandidate, 0); + } + + // create a file entry for a new file. Offset is set automatically + // to 0 to mark the entry as new file entry + maPackedFileEntryVector.emplace_back( + static_cast< sal_uInt32 >(nFileSize), + nCrc32, + rFileCandidate, + bCompress); + + mbChanged = true; + } + + return bNeedToAdd; + } + + bool tryPop(oslFileHandle& rHandle) + { + if (maPackedFileEntryVector.empty()) + return false; + + // already backups there, check if different from last entry + PackedFileEntry& aLastEntry = maPackedFileEntryVector.back(); + + // here the uncompress flag has to be determined, true + // means to add the file compressed, false means to add it + // uncompressed + bool bRetval = aLastEntry.copy_content(rHandle, true); + + if (bRetval) + { + maPackedFileEntryVector.pop_back(); + mbChanged = true; + } + + return bRetval; + } + + void tryReduceToNumBackups(sal_uInt16 nNumBackups) + { + while (maPackedFileEntryVector.size() > nNumBackups) + { + maPackedFileEntryVector.pop_front(); + mbChanged = true; + } + } + + bool empty() const + { + return maPackedFileEntryVector.empty(); + } + }; +} + +namespace comphelper +{ + sal_uInt16 BackupFileHelper::mnMaxAllowedBackups = 10; + bool BackupFileHelper::mbExitWasCalled = false; + bool BackupFileHelper::mbSafeModeDirExists = false; + OUString BackupFileHelper::maInitialBaseURL; + OUString BackupFileHelper::maUserConfigBaseURL; + OUString BackupFileHelper::maUserConfigWorkURL; + OUString BackupFileHelper::maRegModName; + OUString BackupFileHelper::maExt; + + const OUString& BackupFileHelper::getInitialBaseURL() + { + if (maInitialBaseURL.isEmpty()) + { + // try to access user layer configuration file URL, the one that + // points to registrymodifications.xcu + OUString conf("${CONFIGURATION_LAYERS}"); + rtl::Bootstrap::expandMacros(conf); + static const OUStringLiteral aTokenUser(u"user:"); + sal_Int32 nStart(conf.indexOf(aTokenUser)); + + if (-1 != nStart) + { + nStart += aTokenUser.getLength(); + sal_Int32 nEnd(conf.indexOf(' ', nStart)); + + if (-1 == nEnd) + { + nEnd = conf.getLength(); + } + + maInitialBaseURL = conf.copy(nStart, nEnd - nStart); + (void)maInitialBaseURL.startsWith("!", &maInitialBaseURL); + } + + if (!maInitialBaseURL.isEmpty()) + { + // split URL at extension and at last path separator + maUserConfigBaseURL = DirectoryHelper::splitAtLastToken( + DirectoryHelper::splitAtLastToken(maInitialBaseURL, '.', maExt), '/', + maRegModName); + } + + if (!maUserConfigBaseURL.isEmpty()) + { + // check if SafeModeDir exists + mbSafeModeDirExists = DirectoryHelper::dirExists(maUserConfigBaseURL + "/" + getSafeModeName()); + } + + maUserConfigWorkURL = maUserConfigBaseURL; + + if (mbSafeModeDirExists) + { + // adapt work URL to do all repair op's in the correct directory + maUserConfigWorkURL += "/" + getSafeModeName(); + } + } + + return maInitialBaseURL; + } + + const OUString& BackupFileHelper::getSafeModeName() + { + static const OUString aSafeMode("SafeMode"); + + return aSafeMode; + } + + BackupFileHelper::BackupFileHelper() + : mnNumBackups(2), + mnMode(1), + mbActive(false), + mbExtensions(true), + mbCompress(true) + { + OUString sTokenOut; + + // read configuration item 'SecureUserConfig' -> bool on/off + if (rtl::Bootstrap::get("SecureUserConfig", sTokenOut)) + { + mbActive = sTokenOut.toBoolean(); + } + + if (mbActive) + { + // ensure existence + getInitialBaseURL(); + + // if not found, we are out of business (maExt may be empty) + mbActive = !maInitialBaseURL.isEmpty() && !maUserConfigBaseURL.isEmpty() && !maRegModName.isEmpty(); + } + + if (mbActive && rtl::Bootstrap::get("SecureUserConfigNumCopies", sTokenOut)) + { + const sal_uInt16 nConfigNumCopies(static_cast<sal_uInt16>(sTokenOut.toUInt32())); + + // limit to range [1..mnMaxAllowedBackups] + mnNumBackups = std::clamp(mnNumBackups, nConfigNumCopies, mnMaxAllowedBackups); + } + + if (mbActive && rtl::Bootstrap::get("SecureUserConfigMode", sTokenOut)) + { + const sal_uInt16 nMode(static_cast<sal_uInt16>(sTokenOut.toUInt32())); + + // limit to range [0..2] + mnMode = std::min(nMode, sal_uInt16(2)); + } + + if (mbActive && rtl::Bootstrap::get("SecureUserConfigExtensions", sTokenOut)) + { + mbExtensions = sTokenOut.toBoolean(); + } + + if (mbActive && rtl::Bootstrap::get("SecureUserConfigCompress", sTokenOut)) + { + mbCompress = sTokenOut.toBoolean(); + } + } + + void BackupFileHelper::setExitWasCalled() + { + mbExitWasCalled = true; + } + + bool BackupFileHelper::getExitWasCalled() + { + return mbExitWasCalled; + } + + void BackupFileHelper::reactOnSafeMode(bool bSafeMode) + { + // ensure existence of needed paths + getInitialBaseURL(); + + if (maUserConfigBaseURL.isEmpty()) + return; + + if (bSafeMode) + { + if (!mbSafeModeDirExists) + { + std::set< OUString > aExcludeList; + + // do not move SafeMode directory itself + aExcludeList.insert(getSafeModeName()); + + // init SafeMode by creating the 'SafeMode' directory and moving + // all stuff there. All repairs will happen there. Both Dirs have to exist. + // extend maUserConfigWorkURL as needed + maUserConfigWorkURL = maUserConfigBaseURL + "/" + getSafeModeName(); + + osl::Directory::createPath(maUserConfigWorkURL); + DirectoryHelper::moveDirContent(maUserConfigBaseURL, maUserConfigWorkURL, aExcludeList); + + // switch local flag, maUserConfigWorkURL is already reset + mbSafeModeDirExists = true; + } + } + else + { + if (mbSafeModeDirExists) + { + // SafeMode has ended, return to normal mode by moving all content + // from 'SafeMode' directory back to UserDirectory and deleting it. + // Both Dirs have to exist + std::set< OUString > aExcludeList; + + DirectoryHelper::moveDirContent(maUserConfigWorkURL, maUserConfigBaseURL, aExcludeList); + osl::Directory::remove(maUserConfigWorkURL); + + // switch local flag and reset maUserConfigWorkURL + mbSafeModeDirExists = false; + maUserConfigWorkURL = maUserConfigBaseURL; + } + } + } + + void BackupFileHelper::tryPush() + { + // no push when SafeModeDir exists, it may be Office's exit after SafeMode + // where SafeMode flag is already deleted, but SafeModeDir cleanup is not + // done yet (is done at next startup) + if (!mbActive || mbSafeModeDirExists) + return; + + const OUString aPackURL(getPackURL()); + + // ensure dir and file vectors + fillDirFileInfo(); + + // process all files in question recursively + if (!maDirs.empty() || !maFiles.empty()) + { + tryPush_Files( + maDirs, + maFiles, + maUserConfigWorkURL, + aPackURL); + } + } + + void BackupFileHelper::tryPushExtensionInfo() + { + // no push when SafeModeDir exists, it may be Office's exit after SafeMode + // where SafeMode flag is already deleted, but SafeModeDir cleanup is not + // done yet (is done at next startup) + if (mbActive && mbExtensions && !mbSafeModeDirExists) + { + const OUString aPackURL(getPackURL()); + + tryPush_extensionInfo(aPackURL); + } + } + + bool BackupFileHelper::isPopPossible() + { + bool bPopPossible(false); + + if (mbActive) + { + const OUString aPackURL(getPackURL()); + + // ensure dir and file vectors + fillDirFileInfo(); + + // process all files in question recursively + if (!maDirs.empty() || !maFiles.empty()) + { + bPopPossible = isPopPossible_files( + maDirs, + maFiles, + maUserConfigWorkURL, + aPackURL); + } + } + + return bPopPossible; + } + + void BackupFileHelper::tryPop() + { + if (!mbActive) + return; + + bool bDidPop(false); + const OUString aPackURL(getPackURL()); + + // ensure dir and file vectors + fillDirFileInfo(); + + // process all files in question recursively + if (!maDirs.empty() || !maFiles.empty()) + { + bDidPop = tryPop_files( + maDirs, + maFiles, + maUserConfigWorkURL, + aPackURL); + } + + if (bDidPop) + { + // try removal of evtl. empty directory + osl::Directory::remove(aPackURL); + } + } + + bool BackupFileHelper::isPopPossibleExtensionInfo() const + { + bool bPopPossible(false); + + if (mbActive && mbExtensions) + { + const OUString aPackURL(getPackURL()); + + bPopPossible = isPopPossible_extensionInfo(aPackURL); + } + + return bPopPossible; + } + + void BackupFileHelper::tryPopExtensionInfo() + { + if (!(mbActive && mbExtensions)) + return; + + bool bDidPop(false); + const OUString aPackURL(getPackURL()); + + bDidPop = tryPop_extensionInfo(aPackURL); + + if (bDidPop) + { + // try removal of evtl. empty directory + osl::Directory::remove(aPackURL); + } + } + + bool BackupFileHelper::isTryDisableAllExtensionsPossible() + { + // check if there are still enabled extension which can be disabled, + // but as we are now in SafeMode, use XML infos for this since the + // extensions are not loaded from XExtensionManager + class ExtensionInfo aExtensionInfo; + + aExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + return aExtensionInfo.areThereEnabledExtensions(); + } + + void BackupFileHelper::tryDisableAllExtensions() + { + // disable all still enabled extensions, + // but as we are now in SafeMode, use XML infos for this since the + // extensions are not loaded from XExtensionManager + ExtensionInfo aCurrentExtensionInfo; + const ExtensionInfoEntryVector aToBeEnabled{}; + ExtensionInfoEntryVector aToBeDisabled; + + aCurrentExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + const ExtensionInfoEntryVector& rCurrentVector = aCurrentExtensionInfo.getExtensionInfoEntryVector(); + + for (const auto& rCurrentInfo : rCurrentVector) + { + if (rCurrentInfo.isEnabled()) + { + aToBeDisabled.push_back(rCurrentInfo); + } + } + + ExtensionInfo::changeEnableDisableStateInXML(maUserConfigWorkURL, aToBeEnabled, aToBeDisabled); + } + + bool BackupFileHelper::isTryDeinstallUserExtensionsPossible() + { + // check if there are User Extensions installed. + class ExtensionInfo aExtensionInfo; + + aExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + return !aExtensionInfo.getExtensionInfoEntryVector().empty(); + } + + void BackupFileHelper::tryDeinstallUserExtensions() + { + // delete User Extension installs + DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/uno_packages"); + } + + bool BackupFileHelper::isTryResetSharedExtensionsPossible() + { + // check if there are shared Extensions installed + class ExtensionInfo aExtensionInfo; + + aExtensionInfo.createSharedExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + return !aExtensionInfo.getExtensionInfoEntryVector().empty(); + } + + void BackupFileHelper::tryResetSharedExtensions() + { + // reset shared extension info + DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/extensions/shared"); + } + + bool BackupFileHelper::isTryResetBundledExtensionsPossible() + { + // check if there are shared Extensions installed + class ExtensionInfo aExtensionInfo; + + aExtensionInfo.createBundledExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + return !aExtensionInfo.getExtensionInfoEntryVector().empty(); + } + + void BackupFileHelper::tryResetBundledExtensions() + { + // reset shared extension info + DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/extensions/bundled"); + } + + const std::vector< OUString >& BackupFileHelper::getCustomizationDirNames() + { + static std::vector< OUString > aDirNames = + { + "config", // UI config stuff + "registry", // most of the registry stuff + "psprint", // not really needed, can be abandoned + "store", // not really needed, can be abandoned + "temp", // not really needed, can be abandoned + "pack" // own backup dir + }; + + return aDirNames; + } + + const std::vector< OUString >& BackupFileHelper::getCustomizationFileNames() + { + static std::vector< OUString > aFileNames = + { + "registrymodifications.xcu" // personal registry stuff + }; + + return aFileNames; + } + + namespace { + uno::Reference<XElement> lcl_getConfigElement(const uno::Reference<XDocument>& xDocument, const OUString& rPath, + const OUString& rKey, const OUString& rValue) + { + uno::Reference< XElement > itemElement = xDocument->createElement("item"); + itemElement->setAttribute("oor:path", rPath); + + uno::Reference< XElement > propElement = xDocument->createElement("prop"); + propElement->setAttribute("oor:name", rKey); + propElement->setAttribute("oor:op", "replace"); // Replace any other options + + uno::Reference< XElement > valueElement = xDocument->createElement("value"); + uno::Reference< XText > textElement = xDocument->createTextNode(rValue); + + valueElement->appendChild(textElement); + propElement->appendChild(valueElement); + itemElement->appendChild(propElement); + + return itemElement; + } + } + + void BackupFileHelper::tryDisableHWAcceleration() + { + const OUString aRegistryModifications(maUserConfigWorkURL + "/registrymodifications.xcu"); + if (!DirectoryHelper::fileExists(aRegistryModifications)) + return; + + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + uno::Reference< XDocumentBuilder > xBuilder = DocumentBuilder::create(xContext); + uno::Reference< XDocument > xDocument = xBuilder->parseURI(aRegistryModifications); + uno::Reference< XElement > xRootElement = xDocument->getDocumentElement(); + + xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/VCL", + "DisableOpenGL", "true")); + xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/Misc", + "UseOpenCL", "false")); + // Do not disable Skia entirely, just force its CPU-based raster mode. + xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/VCL", + "ForceSkia", "false")); + xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/VCL", + "ForceSkiaRaster", "true")); + + OUString aTempURL; + { + // use the scope to make sure that the temp file gets properly closed before move + + // write back + uno::Reference< xml::sax::XSAXSerializable > xSerializer(xDocument, uno::UNO_QUERY); + + if (!xSerializer.is()) + return; + + // create a SAXWriter + uno::Reference< xml::sax::XWriter > const xSaxWriter = xml::sax::Writer::create(xContext); + uno::Reference< io::XTempFile > xTempFile = io::TempFile::create(xContext); + xTempFile->setRemoveFile(false); // avoid removal of tempfile when leaving the scope + uno::Reference< io::XOutputStream > xOutStrm = xTempFile->getOutputStream(); + + // set output stream and do the serialization + xSaxWriter->setOutputStream(xOutStrm); + xSerializer->serialize(xSaxWriter, uno::Sequence< beans::StringPair >()); + + // get URL from temp file + aTempURL = xTempFile->getUri(); + } + + // copy back file + if (aTempURL.isEmpty() || !DirectoryHelper::fileExists(aTempURL)) + return; + + if (DirectoryHelper::fileExists(aRegistryModifications)) + { + osl::File::remove(aRegistryModifications); + } + + int result = osl::File::move(aTempURL, aRegistryModifications); + SAL_WARN_IF(result != osl::FileBase::E_None, "comphelper.backupfilehelper", "could not copy back modified Extension configuration file"); + } + + bool BackupFileHelper::isTryResetCustomizationsPossible() + { + // return true if not all of the customization selection dirs or files are deleted + const std::vector< OUString >& rDirs = getCustomizationDirNames(); + + for (const auto& a : rDirs) + { + if (DirectoryHelper::dirExists(maUserConfigWorkURL + "/" + a)) + { + return true; + } + } + + const std::vector< OUString >& rFiles = getCustomizationFileNames(); + + for (const auto& b : rFiles) + { + if (DirectoryHelper::fileExists(maUserConfigWorkURL + "/" + b)) + { + return true; + } + } + + return false; + } + + void BackupFileHelper::tryResetCustomizations() + { + // delete all of the customization selection dirs + const std::vector< OUString >& rDirs = getCustomizationDirNames(); + + for (const auto& a : rDirs) + { + DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/" + a); + } + + const std::vector< OUString >& rFiles = getCustomizationFileNames(); + + for (const auto& b : rFiles) + { + osl::File::remove(maUserConfigWorkURL + "/" + b); + } + } + + void BackupFileHelper::tryResetUserProfile() + { + // completely delete the current UserProfile + DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL); + } + + const OUString& BackupFileHelper::getUserProfileURL() + { + return maUserConfigBaseURL; + } + + const OUString& BackupFileHelper::getUserProfileWorkURL() + { + return maUserConfigWorkURL; + } + + /////////////////// helpers /////////////////////// + + OUString BackupFileHelper::getPackURL() + { + return OUString(maUserConfigWorkURL + "/pack"); + } + + /////////////////// file push helpers /////////////////////// + + bool BackupFileHelper::tryPush_Files( + const std::set< OUString >& rDirs, + const std::set< std::pair< OUString, OUString > >& rFiles, + std::u16string_view rSourceURL, // source dir without trailing '/' + const OUString& rTargetURL // target dir without trailing '/' + ) + { + bool bDidPush(false); + osl::Directory::createPath(rTargetURL); + + // process files + for (const auto& file : rFiles) + { + bDidPush |= tryPush_file( + rSourceURL, + rTargetURL, + file.first, + file.second); + } + + // process dirs + for (const auto& dir : rDirs) + { + OUString aNewSourceURL(OUString::Concat(rSourceURL) + "/" + dir); + OUString aNewTargetURL(rTargetURL + "/" + dir); + std::set< OUString > aNewDirs; + std::set< std::pair< OUString, OUString > > aNewFiles; + + DirectoryHelper::scanDirsAndFiles( + aNewSourceURL, + aNewDirs, + aNewFiles); + + if (!aNewDirs.empty() || !aNewFiles.empty()) + { + bDidPush |= tryPush_Files( + aNewDirs, + aNewFiles, + aNewSourceURL, + aNewTargetURL); + } + } + + if (!bDidPush) + { + // try removal of evtl. empty directory + osl::Directory::remove(rTargetURL); + } + + return bDidPush; + } + + bool BackupFileHelper::tryPush_file( + std::u16string_view rSourceURL, // source dir without trailing '/' + std::u16string_view rTargetURL, // target dir without trailing '/' + std::u16string_view rName, // filename + std::u16string_view rExt // extension (or empty) + ) + { + const OUString aFileURL(createFileURL(rSourceURL, rName, rExt)); + + if (DirectoryHelper::fileExists(aFileURL)) + { + const OUString aPackURL(createPackURL(rTargetURL, rName)); + PackedFile aPackedFile(aPackURL); + FileSharedPtr aBaseFile = std::make_shared<osl::File>(aFileURL); + + if (aPackedFile.tryPush(aBaseFile, mbCompress)) + { + // reduce to allowed number and flush + aPackedFile.tryReduceToNumBackups(mnNumBackups); + aPackedFile.flush(); + + return true; + } + } + + return false; + } + + /////////////////// file pop possibilities helper /////////////////////// + + bool BackupFileHelper::isPopPossible_files( + const std::set< OUString >& rDirs, + const std::set< std::pair< OUString, OUString > >& rFiles, + std::u16string_view rSourceURL, // source dir without trailing '/' + std::u16string_view rTargetURL // target dir without trailing '/' + ) + { + bool bPopPossible(false); + + // process files + for (const auto& file : rFiles) + { + bPopPossible |= isPopPossible_file( + rSourceURL, + rTargetURL, + file.first, + file.second); + } + + // process dirs + for (const auto& dir : rDirs) + { + OUString aNewSourceURL(OUString::Concat(rSourceURL) + "/" + dir); + OUString aNewTargetURL(OUString::Concat(rTargetURL) + "/" + dir); + std::set< OUString > aNewDirs; + std::set< std::pair< OUString, OUString > > aNewFiles; + + DirectoryHelper::scanDirsAndFiles( + aNewSourceURL, + aNewDirs, + aNewFiles); + + if (!aNewDirs.empty() || !aNewFiles.empty()) + { + bPopPossible |= isPopPossible_files( + aNewDirs, + aNewFiles, + aNewSourceURL, + aNewTargetURL); + } + } + + return bPopPossible; + } + + bool BackupFileHelper::isPopPossible_file( + std::u16string_view rSourceURL, // source dir without trailing '/' + std::u16string_view rTargetURL, // target dir without trailing '/' + std::u16string_view rName, // filename + std::u16string_view rExt // extension (or empty) + ) + { + const OUString aFileURL(createFileURL(rSourceURL, rName, rExt)); + + if (DirectoryHelper::fileExists(aFileURL)) + { + const OUString aPackURL(createPackURL(rTargetURL, rName)); + PackedFile aPackedFile(aPackURL); + + return !aPackedFile.empty(); + } + + return false; + } + + /////////////////// file pop helpers /////////////////////// + + bool BackupFileHelper::tryPop_files( + const std::set< OUString >& rDirs, + const std::set< std::pair< OUString, OUString > >& rFiles, + std::u16string_view rSourceURL, // source dir without trailing '/' + const OUString& rTargetURL // target dir without trailing '/' + ) + { + bool bDidPop(false); + + // process files + for (const auto& file : rFiles) + { + bDidPop |= tryPop_file( + rSourceURL, + rTargetURL, + file.first, + file.second); + } + + // process dirs + for (const auto& dir : rDirs) + { + OUString aNewSourceURL(OUString::Concat(rSourceURL) + "/" + dir); + OUString aNewTargetURL(rTargetURL + "/" + dir); + std::set< OUString > aNewDirs; + std::set< std::pair< OUString, OUString > > aNewFiles; + + DirectoryHelper::scanDirsAndFiles( + aNewSourceURL, + aNewDirs, + aNewFiles); + + if (!aNewDirs.empty() || !aNewFiles.empty()) + { + bDidPop |= tryPop_files( + aNewDirs, + aNewFiles, + aNewSourceURL, + aNewTargetURL); + } + } + + if (bDidPop) + { + // try removal of evtl. empty directory + osl::Directory::remove(rTargetURL); + } + + return bDidPop; + } + + bool BackupFileHelper::tryPop_file( + std::u16string_view rSourceURL, // source dir without trailing '/' + std::u16string_view rTargetURL, // target dir without trailing '/' + std::u16string_view rName, // filename + std::u16string_view rExt // extension (or empty) + ) + { + const OUString aFileURL(createFileURL(rSourceURL, rName, rExt)); + + if (!DirectoryHelper::fileExists(aFileURL)) + return false; + + // try Pop for base file + const OUString aPackURL(createPackURL(rTargetURL, rName)); + PackedFile aPackedFile(aPackURL); + + if (aPackedFile.empty()) + return false; + + oslFileHandle aHandle; + OUString aTempURL; + + // open target temp file - it exists until deleted + if (osl::File::E_None != osl::FileBase::createTempFile(nullptr, &aHandle, &aTempURL)) + return false; + + bool bRetval(aPackedFile.tryPop(aHandle)); + + // close temp file (in all cases) - it exists until deleted + osl_closeFile(aHandle); + + if (bRetval) + { + // copy over existing file by first deleting original + // and moving the temp file to old original + osl::File::remove(aFileURL); + osl::File::move(aTempURL, aFileURL); + + // reduce to allowed number and flush + aPackedFile.tryReduceToNumBackups(mnNumBackups); + aPackedFile.flush(); + } + + // delete temp file (in all cases - it may be moved already) + osl::File::remove(aTempURL); + + return bRetval; + } + + /////////////////// ExtensionInfo helpers /////////////////////// + + bool BackupFileHelper::tryPush_extensionInfo( + std::u16string_view rTargetURL // target dir without trailing '/' + ) + { + ExtensionInfo aExtensionInfo; + OUString aTempURL; + bool bRetval(false); + + // create current configuration and write to temp file - it exists until deleted + if (aExtensionInfo.createTempFile(aTempURL)) + { + const OUString aPackURL(createPackURL(rTargetURL, u"ExtensionInfo")); + PackedFile aPackedFile(aPackURL); + FileSharedPtr aBaseFile = std::make_shared<osl::File>(aTempURL); + + if (aPackedFile.tryPush(aBaseFile, mbCompress)) + { + // reduce to allowed number and flush + aPackedFile.tryReduceToNumBackups(mnNumBackups); + aPackedFile.flush(); + bRetval = true; + } + } + + // delete temp file (in all cases) + osl::File::remove(aTempURL); + return bRetval; + } + + bool BackupFileHelper::isPopPossible_extensionInfo( + std::u16string_view rTargetURL // target dir without trailing '/' + ) + { + // extensionInfo always exists internally, no test needed + const OUString aPackURL(createPackURL(rTargetURL, u"ExtensionInfo")); + PackedFile aPackedFile(aPackURL); + + return !aPackedFile.empty(); + } + + bool BackupFileHelper::tryPop_extensionInfo( + std::u16string_view rTargetURL // target dir without trailing '/' + ) + { + // extensionInfo always exists internally, no test needed + const OUString aPackURL(createPackURL(rTargetURL, u"ExtensionInfo")); + PackedFile aPackedFile(aPackURL); + + if (aPackedFile.empty()) + return false; + + oslFileHandle aHandle; + OUString aTempURL; + + // open target temp file - it exists until deleted + if (osl::File::E_None != osl::FileBase::createTempFile(nullptr, &aHandle, &aTempURL)) + return false; + + bool bRetval(aPackedFile.tryPop(aHandle)); + + // close temp file (in all cases) - it exists until deleted + osl_closeFile(aHandle); + + if (bRetval) + { + // last config is in temp file, load it to ExtensionInfo + ExtensionInfo aLoadedExtensionInfo; + FileSharedPtr aBaseFile = std::make_shared<osl::File>(aTempURL); + + if (osl::File::E_None == aBaseFile->open(osl_File_OpenFlag_Read)) + { + if (aLoadedExtensionInfo.read_entries(aBaseFile)) + { + // get current extension info, but from XML config files + ExtensionInfo aCurrentExtensionInfo; + + aCurrentExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + // now we have loaded last_working (aLoadedExtensionInfo) and + // current (aCurrentExtensionInfo) ExtensionInfo and may react on + // differences by de/activating these as needed + const ExtensionInfoEntryVector& aUserEntries = aCurrentExtensionInfo.getExtensionInfoEntryVector(); + const ExtensionInfoEntryVector& rLoadedVector = aLoadedExtensionInfo.getExtensionInfoEntryVector(); + ExtensionInfoEntryVector aToBeDisabled; + ExtensionInfoEntryVector aToBeEnabled; + + for (const auto& rCurrentInfo : aUserEntries) + { + const ExtensionInfoEntry* pLoadedInfo = nullptr; + + for (const auto& rLoadedInfo : rLoadedVector) + { + if (rCurrentInfo.isSameExtension(rLoadedInfo)) + { + pLoadedInfo = &rLoadedInfo; + break; + } + } + + if (nullptr != pLoadedInfo) + { + // loaded info contains information about the Extension rCurrentInfo + const bool bCurrentEnabled(rCurrentInfo.isEnabled()); + const bool bLoadedEnabled(pLoadedInfo->isEnabled()); + + if (bCurrentEnabled && !bLoadedEnabled) + { + aToBeDisabled.push_back(rCurrentInfo); + } + else if (!bCurrentEnabled && bLoadedEnabled) + { + aToBeEnabled.push_back(rCurrentInfo); + } + } + else + { + // There is no loaded info about the Extension rCurrentInfo. + // It needs to be disabled + if (rCurrentInfo.isEnabled()) + { + aToBeDisabled.push_back(rCurrentInfo); + } + } + } + + if (!aToBeDisabled.empty() || !aToBeEnabled.empty()) + { + ExtensionInfo::changeEnableDisableStateInXML(maUserConfigWorkURL, aToBeEnabled, aToBeDisabled); + } + + bRetval = true; + } + } + + // reduce to allowed number and flush + aPackedFile.tryReduceToNumBackups(mnNumBackups); + aPackedFile.flush(); + } + + // delete temp file (in all cases - it may be moved already) + osl::File::remove(aTempURL); + + return bRetval; + } + + /////////////////// FileDirInfo helpers /////////////////////// + + void BackupFileHelper::fillDirFileInfo() + { + if (!maDirs.empty() || !maFiles.empty()) + { + // already done + return; + } + + // Information about the configuration and the role/purpose of directories in + // the UserConfiguration is taken from: https://wiki.documentfoundation.org/UserProfile + + // fill dir and file info list to work with dependent on work mode + switch (mnMode) + { + case 0: + { + // simple mode: add just registrymodifications + // (the orig file in maInitialBaseURL) + maFiles.insert(std::pair< OUString, OUString >(maRegModName, maExt)); + break; + } + case 1: + { + // defined mode: Add a selection of dirs containing User-Defined and thus + // valuable configuration information. + // This is clearly discussable in every single point and may be adapted/corrected + // over time. Main focus is to secure User-Defined/adapted values + + // add registrymodifications (the orig file in maInitialBaseURL) + maFiles.insert(std::pair< OUString, OUString >(maRegModName, maExt)); + + // User-defined substitution table (Tools/AutoCorrect) + maDirs.insert("autocorr"); + + // User-Defined AutoText (Edit/AutoText) + maDirs.insert("autotext"); + + // User-defined Macros + maDirs.insert("basic"); + + // User-adapted toolbars for modules + maDirs.insert("config"); + + // Initial and User-defined Databases + maDirs.insert("database"); + + // most part of registry files + maDirs.insert("registry"); + + // User-Defined Scripts + maDirs.insert("Scripts"); + + // Template files + maDirs.insert("template"); + + // Custom Dictionaries + maDirs.insert("wordbook"); + + // Questionable - where and how is Extension stuff held and how + // does this interact with enabled/disabled states which are extra handled? + // Keep out of business until deeper evaluated + // + // maDirs.insert("extensions"); + // maDirs.insert("uno-packages"); + break; + } + case 2: + { + // whole directory. To do so, scan directory and exclude some dirs + // from which we know they do not need to be secured explicitly. This + // should already include registrymodifications, too. + DirectoryHelper::scanDirsAndFiles( + maUserConfigWorkURL, + maDirs, + maFiles); + + // should not exist, but for the case an error occurred and it got + // copied somehow, avoid further recursive copying/saving + maDirs.erase("SafeMode"); + + // not really needed, can be abandoned + maDirs.erase("psprint"); + + // not really needed, can be abandoned + maDirs.erase("store"); + + // not really needed, can be abandoned + maDirs.erase("temp"); + + // exclude own backup dir to avoid recursion + maDirs.erase("pack"); + + break; + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/base64.cxx b/comphelper/source/misc/base64.cxx new file mode 100644 index 000000000..5e3e3ca0a --- /dev/null +++ b/comphelper/source/misc/base64.cxx @@ -0,0 +1,220 @@ +/* -*- 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 <cstddef> + +#include <comphelper/base64.hxx> + +#include <com/sun/star/uno/Sequence.hxx> + +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +using namespace com::sun::star; + +namespace comphelper { + +const + char aBase64EncodeTable[] = + { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; + +const + sal_uInt8 aBase64DecodeTable[] = + { 62,255,255,255, 63, // 43-47 +// + / + + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, // 48-63 +// 0 1 2 3 4 5 6 7 8 9 = + + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79 +// A B C D E F G H I J K L M N O + + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, // 80-95 +// P Q R S T U V W X Y Z + + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111 +// a b c d e f g h i j k l m n o + + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; // 112-123 +// p q r s t u v w x y z + + +static void ThreeByteToFourByte(const sal_Int8* pBuffer, const sal_Int32 nStart, const sal_Int32 nFullLen, char* aCharBuffer) +{ + sal_Int32 nLen(nFullLen - nStart); + if (nLen > 3) + nLen = 3; + if (nLen == 0) + { + return; + } + + sal_Int32 nBinaer; + switch (nLen) + { + case 1: + { + nBinaer = static_cast<sal_uInt8>(pBuffer[nStart + 0]) << 16; + } + break; + case 2: + { + nBinaer = (static_cast<sal_uInt8>(pBuffer[nStart + 0]) << 16) + + (static_cast<sal_uInt8>(pBuffer[nStart + 1]) << 8); + } + break; + default: + { + nBinaer = (static_cast<sal_uInt8>(pBuffer[nStart + 0]) << 16) + + (static_cast<sal_uInt8>(pBuffer[nStart + 1]) << 8) + + static_cast<sal_uInt8>(pBuffer[nStart + 2]); + } + break; + } + + aCharBuffer[0] = aCharBuffer[1] = aCharBuffer[2] = aCharBuffer[3] = '='; + + sal_uInt8 nIndex (static_cast<sal_uInt8>((nBinaer & 0xFC0000) >> 18)); + aCharBuffer[0] = aBase64EncodeTable [nIndex]; + + nIndex = static_cast<sal_uInt8>((nBinaer & 0x3F000) >> 12); + aCharBuffer[1] = aBase64EncodeTable [nIndex]; + if (nLen > 1) + { + nIndex = static_cast<sal_uInt8>((nBinaer & 0xFC0) >> 6); + aCharBuffer[2] = aBase64EncodeTable [nIndex]; + if (nLen > 2) + { + nIndex = static_cast<sal_uInt8>((nBinaer & 0x3F)); + aCharBuffer[3] = aBase64EncodeTable [nIndex]; + } + } +} + +void Base64::encode(OStringBuffer& aStrBuffer, const uno::Sequence<sal_Int8>& aPass) +{ + sal_Int32 i(0); + sal_Int32 nBufferLength(aPass.getLength()); + const sal_Int8* pBuffer = aPass.getConstArray(); + while (i < nBufferLength) + { + char aCharBuffer[4]; + ThreeByteToFourByte(pBuffer, i, nBufferLength, aCharBuffer); + aStrBuffer.append(aCharBuffer, std::size(aCharBuffer)); + i += 3; + } +} + +void Base64::encode(OUStringBuffer& aStrBuffer, const uno::Sequence<sal_Int8>& aPass) +{ + sal_Int32 i(0); + sal_Int32 nBufferLength(aPass.getLength()); + const sal_Int8* pBuffer = aPass.getConstArray(); + while (i < nBufferLength) + { + char aCharBuffer[4]; + ThreeByteToFourByte(pBuffer, i, nBufferLength, aCharBuffer); + aStrBuffer.appendAscii(aCharBuffer, std::size(aCharBuffer)); + i += 3; + } +} + +void Base64::decode(uno::Sequence<sal_Int8>& aBuffer, std::u16string_view sBuffer) +{ + std::size_t nCharsDecoded = decodeSomeChars( aBuffer, sBuffer ); + OSL_ENSURE( nCharsDecoded == sBuffer.size(), "some bytes left in base64 decoding!" ); +} + +std::size_t Base64::decodeSomeChars(uno::Sequence<sal_Int8>& rOutBuffer, std::u16string_view rInBuffer) +{ + std::size_t nInBufferLen = rInBuffer.size(); + std::size_t nMinOutBufferLen = (nInBufferLen / 4) * 3; + if( o3tl::make_unsigned(rOutBuffer.getLength()) < nMinOutBufferLen ) + rOutBuffer.realloc( nMinOutBufferLen ); + + const sal_Unicode *pInBuffer = rInBuffer.data(); + sal_Int8 *pOutBuffer = rOutBuffer.getArray(); + sal_Int8 *pOutBufferStart = pOutBuffer; + std::size_t nCharsDecoded = 0; + + sal_uInt8 aDecodeBuffer[4]; + sal_Int32 nBytesToDecode = 0; + sal_Int32 nBytesGotFromDecoding = 3; + std::size_t nInBufferPos= 0; + while( nInBufferPos < nInBufferLen ) + { + sal_Unicode cChar = *pInBuffer; + if( cChar >= '+' && cChar <= 'z' ) + { + sal_uInt8 nByte = aBase64DecodeTable[cChar-'+']; + if( nByte != 255 ) + { + // We have found a valid character! + aDecodeBuffer[nBytesToDecode++] = nByte; + + // One '=' character at the end means 2 out bytes + // Two '=' characters at the end mean 1 out bytes + if( '=' == cChar && nBytesToDecode > 2 ) + nBytesGotFromDecoding--; + if( 4 == nBytesToDecode ) + { + // Four characters found, so we may convert now! + sal_uInt32 nOut = (aDecodeBuffer[0] << 18) + + (aDecodeBuffer[1] << 12) + + (aDecodeBuffer[2] << 6) + + aDecodeBuffer[3]; + + *pOutBuffer++ = static_cast<sal_Int8>((nOut & 0xff0000) >> 16); + if( nBytesGotFromDecoding > 1 ) + *pOutBuffer++ = static_cast<sal_Int8>((nOut & 0xff00) >> 8); + if( nBytesGotFromDecoding > 2 ) + *pOutBuffer++ = static_cast<sal_Int8>(nOut & 0xff); + nCharsDecoded = nInBufferPos + 1; + nBytesToDecode = 0; + nBytesGotFromDecoding = 3; + } + } + else + { + nCharsDecoded++; + } + } + else + { + nCharsDecoded++; + } + + nInBufferPos++; + pInBuffer++; + } + if( (pOutBuffer - pOutBufferStart) != rOutBuffer.getLength() ) + rOutBuffer.realloc( pOutBuffer - pOutBufferStart ); + + return nCharsDecoded; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/compbase.cxx b/comphelper/source/misc/compbase.cxx new file mode 100644 index 000000000..f8a8897b7 --- /dev/null +++ b/comphelper/source/misc/compbase.cxx @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <comphelper/compbase.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +namespace comphelper +{ +WeakComponentImplHelperBase::~WeakComponentImplHelperBase() {} + +// css::lang::XComponent +void SAL_CALL WeakComponentImplHelperBase::dispose() +{ + std::unique_lock aGuard(m_aMutex); + if (m_bDisposed) + return; + m_bDisposed = true; + disposing(aGuard); + if (!aGuard.owns_lock()) + aGuard.lock(); + css::lang::EventObject aEvt(static_cast<OWeakObject*>(this)); + maEventListeners.disposeAndClear(aGuard, aEvt); +} + +void WeakComponentImplHelperBase::disposing(std::unique_lock<std::mutex>&) {} + +void SAL_CALL WeakComponentImplHelperBase::addEventListener( + css::uno::Reference<css::lang::XEventListener> const& rxListener) +{ + std::unique_lock aGuard(m_aMutex); + if (m_bDisposed) + return; + maEventListeners.addInterface(aGuard, rxListener); +} + +void SAL_CALL WeakComponentImplHelperBase::removeEventListener( + css::uno::Reference<css::lang::XEventListener> const& rxListener) +{ + std::unique_lock aGuard(m_aMutex); + maEventListeners.removeInterface(aGuard, rxListener); +} + +css::uno::Any SAL_CALL WeakComponentImplHelperBase::queryInterface(css::uno::Type const& rType) +{ + css::uno::Any aReturn = ::cppu::queryInterface(rType, static_cast<css::uno::XWeak*>(this), + static_cast<css::lang::XComponent*>(this)); + if (aReturn.hasValue()) + return aReturn; + return OWeakObject::queryInterface(rType); +} + +static void checkInterface(css::uno::Type const& rType) +{ + if (css::uno::TypeClass_INTERFACE != rType.getTypeClass()) + { + OUString msg("querying for interface \"" + rType.getTypeName() + "\": no interface type!"); + SAL_WARN("cppuhelper", msg); + throw css::uno::RuntimeException(msg); + } +} + +static bool isXInterface(rtl_uString* pStr) +{ + return OUString::unacquired(&pStr) == "com.sun.star.uno.XInterface"; +} + +static bool td_equals(typelib_TypeDescriptionReference const* pTDR1, + typelib_TypeDescriptionReference const* pTDR2) +{ + return ((pTDR1 == pTDR2) + || OUString::unacquired(&pTDR1->pTypeName) == OUString::unacquired(&pTDR2->pTypeName)); +} + +static cppu::type_entry* getTypeEntries(cppu::class_data* cd) +{ + cppu::type_entry* pEntries = cd->m_typeEntries; + if (!cd->m_storedTypeRefs) // not inited? + { + static std::mutex aMutex; + std::scoped_lock guard(aMutex); + if (!cd->m_storedTypeRefs) // not inited? + { + // get all types + for (sal_Int32 n = cd->m_nTypes; n--;) + { + cppu::type_entry* pEntry = &pEntries[n]; + css::uno::Type const& rType = (*pEntry->m_type.getCppuType)(nullptr); + OSL_ENSURE(rType.getTypeClass() == css::uno::TypeClass_INTERFACE, + "### wrong helper init: expected interface!"); + OSL_ENSURE( + !isXInterface(rType.getTypeLibType()->pTypeName), + "### want to implement XInterface: template argument is XInterface?!?!?!"); + if (rType.getTypeClass() != css::uno::TypeClass_INTERFACE) + { + OUString msg("type \"" + rType.getTypeName() + "\" is no interface type!"); + SAL_WARN("cppuhelper", msg); + throw css::uno::RuntimeException(msg); + } + // ref is statically held by getCppuType() + pEntry->m_type.typeRef = rType.getTypeLibType(); + } + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + cd->m_storedTypeRefs = true; + } + } + else + { + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + } + return pEntries; +} + +static void* makeInterface(sal_IntPtr nOffset, void* that) +{ + return (static_cast<char*>(that) + nOffset); +} + +static bool recursivelyFindType(typelib_TypeDescriptionReference const* demandedType, + typelib_InterfaceTypeDescription const* type, sal_IntPtr* offset) +{ + // This code assumes that the vtables of a multiple-inheritance class (the + // offset amount by which to adjust the this pointer) follow one another in + // the object layout, and that they contain slots for the inherited classes + // in a specific order. In theory, that need not hold for any given + // platform; in practice, it seems to work well on all supported platforms: +next: + for (sal_Int32 i = 0; i < type->nBaseTypes; ++i) + { + if (i > 0) + { + *offset += sizeof(void*); + } + typelib_InterfaceTypeDescription const* base = type->ppBaseTypes[i]; + // ignore XInterface: + if (base->nBaseTypes > 0) + { + if (td_equals(reinterpret_cast<typelib_TypeDescriptionReference const*>(base), + demandedType)) + { + return true; + } + // Profiling showed that it is important to speed up the common case + // of only one base: + if (type->nBaseTypes == 1) + { + type = base; + goto next; + } + if (recursivelyFindType(demandedType, base, offset)) + { + return true; + } + } + } + return false; +} + +static void* queryDeepNoXInterface(typelib_TypeDescriptionReference const* pDemandedTDR, + cppu::class_data* cd, void* that) +{ + cppu::type_entry* pEntries = getTypeEntries(cd); + sal_Int32 nTypes = cd->m_nTypes; + sal_Int32 n; + + // try top interfaces without getting td + for (n = 0; n < nTypes; ++n) + { + if (td_equals(pEntries[n].m_type.typeRef, pDemandedTDR)) + { + return makeInterface(pEntries[n].m_offset, that); + } + } + // query deep getting td + for (n = 0; n < nTypes; ++n) + { + typelib_TypeDescription* pTD = nullptr; + TYPELIB_DANGER_GET(&pTD, pEntries[n].m_type.typeRef); + if (pTD) + { + // exclude top (already tested) and bottom (XInterface) interface + OSL_ENSURE(reinterpret_cast<typelib_InterfaceTypeDescription*>(pTD)->nBaseTypes > 0, + "### want to implement XInterface:" + " template argument is XInterface?!?!?!"); + sal_IntPtr offset = pEntries[n].m_offset; + bool found = recursivelyFindType( + pDemandedTDR, reinterpret_cast<typelib_InterfaceTypeDescription*>(pTD), &offset); + TYPELIB_DANGER_RELEASE(pTD); + if (found) + { + return makeInterface(offset, that); + } + } + else + { + OUString msg("cannot get type description for type \"" + + OUString::unacquired(&pEntries[n].m_type.typeRef->pTypeName) + "\"!"); + SAL_WARN("cppuhelper", msg); + throw css::uno::RuntimeException(msg); + } + } + return nullptr; +} + +css::uno::Any WeakComponentImplHelper_query(css::uno::Type const& rType, cppu::class_data* cd, + WeakComponentImplHelperBase* pBase) +{ + checkInterface(rType); + typelib_TypeDescriptionReference* pTDR = rType.getTypeLibType(); + + // shortcut XInterface to WeakComponentImplHelperBase + if (!isXInterface(pTDR->pTypeName)) + { + void* p = queryDeepNoXInterface(pTDR, cd, pBase); + if (p) + { + return css::uno::Any(&p, pTDR); + } + } + return pBase->comphelper::WeakComponentImplHelperBase::queryInterface(rType); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/comphelper/source/misc/componentbase.cxx b/comphelper/source/misc/componentbase.cxx new file mode 100644 index 000000000..9baec2363 --- /dev/null +++ b/comphelper/source/misc/componentbase.cxx @@ -0,0 +1,59 @@ +/* -*- 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/componentbase.hxx> + +#include <com/sun/star/lang/NotInitializedException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> + + +namespace comphelper +{ + + + using ::com::sun::star::lang::NotInitializedException; + using ::com::sun::star::lang::DisposedException; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + + void ComponentBase::checkDisposed( GuardAccess ) const + { + if ( m_rBHelper.bDisposed ) + throw DisposedException( OUString(), getComponent() ); + } + + + void ComponentBase::checkInitialized( GuardAccess ) const + { + if ( !m_bInitialized ) + throw NotInitializedException( OUString(), getComponent() ); + } + + + Reference< XInterface > ComponentBase::getComponent() + { + return nullptr; + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/configuration.cxx b/comphelper/source/misc/configuration.cxx new file mode 100644 index 000000000..59631dbcc --- /dev/null +++ b/comphelper/source/misc/configuration.cxx @@ -0,0 +1,279 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <cassert> +#include <map> +#include <memory> +#include <mutex> +#include <string_view> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/configuration/ReadOnlyAccess.hpp> +#include <com/sun/star/configuration/ReadWriteAccess.hpp> +#include <com/sun/star/configuration/XReadWriteAccess.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/container/XHierarchicalNameReplace.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/lang/XLocalizable.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <comphelper/solarmutex.hxx> +#include <comphelper/configuration.hxx> +#include <comphelper/configurationlistener.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <i18nlangtag/languagetag.hxx> + +namespace com::sun::star::uno { class XComponentContext; } + +namespace { + +OUString getDefaultLocale( + css::uno::Reference< css::uno::XComponentContext > const & context) +{ + return LanguageTag( + css::uno::Reference< css::lang::XLocalizable >( + css::configuration::theDefaultProvider::get(context), + css::uno::UNO_QUERY_THROW)-> + getLocale()).getBcp47(false); +} + +OUString extendLocalizedPath(std::u16string_view path, OUString const & locale) { + SAL_WARN_IF( + locale.match("*"), "comphelper", + "Locale \"" << locale << "\" starts with \"*\""); + assert(locale.indexOf('&') == -1); + assert(locale.indexOf('"') == -1); + assert(locale.indexOf('\'') == -1); + return OUString::Concat(path) + "/['*" + locale + "']"; +} + +} + +std::shared_ptr< comphelper::ConfigurationChanges > +comphelper::ConfigurationChanges::create() +{ + return detail::ConfigurationWrapper::get().createChanges(); +} + +comphelper::ConfigurationChanges::~ConfigurationChanges() {} + +void comphelper::ConfigurationChanges::commit() const { + access_->commitChanges(); +} + +comphelper::ConfigurationChanges::ConfigurationChanges( + css::uno::Reference< css::uno::XComponentContext > const & context): + access_( + css::configuration::ReadWriteAccess::create( + context, getDefaultLocale(context))) +{} + +void comphelper::ConfigurationChanges::setPropertyValue( + OUString const & path, css::uno::Any const & value) const +{ + access_->replaceByHierarchicalName(path, value); +} + +css::uno::Reference< css::container::XHierarchicalNameReplace > +comphelper::ConfigurationChanges::getGroup(OUString const & path) const +{ + return css::uno::Reference< css::container::XHierarchicalNameReplace >( + access_->getByHierarchicalName(path), css::uno::UNO_QUERY_THROW); +} + +css::uno::Reference< css::container::XNameContainer > +comphelper::ConfigurationChanges::getSet(OUString const & path) const +{ + return css::uno::Reference< css::container::XNameContainer >( + access_->getByHierarchicalName(path), css::uno::UNO_QUERY_THROW); +} + +comphelper::detail::ConfigurationWrapper const & +comphelper::detail::ConfigurationWrapper::get() +{ + static comphelper::detail::ConfigurationWrapper WRAPPER; + return WRAPPER; +} + +comphelper::detail::ConfigurationWrapper::ConfigurationWrapper(): + context_(comphelper::getProcessComponentContext()), + access_(css::configuration::ReadWriteAccess::create(context_, "*")) +{} + +comphelper::detail::ConfigurationWrapper::~ConfigurationWrapper() {} + +bool comphelper::detail::ConfigurationWrapper::isReadOnly(OUString const & path) + const +{ + return + (access_->getPropertyByHierarchicalName(path).Attributes + & css::beans::PropertyAttribute::READONLY) + != 0; +} + +css::uno::Any comphelper::detail::ConfigurationWrapper::getPropertyValue(std::u16string_view path) const +{ + // Cache the configuration access, since some of the keys are used in hot code. + // Note that this cache is only used by the officecfg:: auto-generated code, using it for anything + // else would be unwise because the cache could end up containing stale entries. + static std::mutex gMutex; + static std::map<OUString, css::uno::Reference< css::container::XNameAccess >> gAccessMap; + + sal_Int32 idx = path.rfind('/'); + assert(idx!=-1); + OUString parentPath(path.substr(0, idx)); + OUString childName(path.substr(idx+1)); + + std::scoped_lock aGuard(gMutex); + + // check cache + auto it = gAccessMap.find(parentPath); + if (it == gAccessMap.end()) + { + // not in the cache, look it up + css::uno::Reference<css::container::XNameAccess> access( + access_->getByHierarchicalName(parentPath), css::uno::UNO_QUERY_THROW); + it = gAccessMap.emplace(parentPath, access).first; + } + return it->second->getByName(childName); +} + +void comphelper::detail::ConfigurationWrapper::setPropertyValue( + std::shared_ptr< ConfigurationChanges > const & batch, + OUString const & path, css::uno::Any const & value) +{ + assert(batch); + batch->setPropertyValue(path, value); +} + +css::uno::Any +comphelper::detail::ConfigurationWrapper::getLocalizedPropertyValue( + std::u16string_view path) const +{ + return access_->getByHierarchicalName( + extendLocalizedPath(path, getDefaultLocale(context_))); +} + +void comphelper::detail::ConfigurationWrapper::setLocalizedPropertyValue( + std::shared_ptr< ConfigurationChanges > const & batch, + OUString const & path, css::uno::Any const & value) +{ + assert(batch); + batch->setPropertyValue(path, value); +} + +css::uno::Reference< css::container::XHierarchicalNameAccess > +comphelper::detail::ConfigurationWrapper::getGroupReadOnly( + OUString const & path) const +{ + return css::uno::Reference< css::container::XHierarchicalNameAccess >( + (css::configuration::ReadOnlyAccess::create( + context_, getDefaultLocale(context_))-> + getByHierarchicalName(path)), + css::uno::UNO_QUERY_THROW); +} + +css::uno::Reference< css::container::XHierarchicalNameReplace > +comphelper::detail::ConfigurationWrapper::getGroupReadWrite( + std::shared_ptr< ConfigurationChanges > const & batch, + OUString const & path) +{ + assert(batch); + return batch->getGroup(path); +} + +css::uno::Reference< css::container::XNameAccess > +comphelper::detail::ConfigurationWrapper::getSetReadOnly( + OUString const & path) const +{ + return css::uno::Reference< css::container::XNameAccess >( + (css::configuration::ReadOnlyAccess::create( + context_, getDefaultLocale(context_))-> + getByHierarchicalName(path)), + css::uno::UNO_QUERY_THROW); +} + +css::uno::Reference< css::container::XNameContainer > +comphelper::detail::ConfigurationWrapper::getSetReadWrite( + std::shared_ptr< ConfigurationChanges > const & batch, + OUString const & path) +{ + assert(batch); + return batch->getSet(path); +} + +std::shared_ptr< comphelper::ConfigurationChanges > +comphelper::detail::ConfigurationWrapper::createChanges() const { + return std::shared_ptr< ConfigurationChanges >( + new ConfigurationChanges(context_)); +} + +void comphelper::ConfigurationListener::addListener(ConfigurationListenerPropertyBase *pListener) +{ + maListeners.push_back( pListener ); + mxConfig->addPropertyChangeListener( pListener->maName, this ); + pListener->setProperty( mxConfig->getPropertyValue( pListener->maName ) ); +} + +void comphelper::ConfigurationListener::removeListener(ConfigurationListenerPropertyBase *pListener) +{ + auto it = std::find( maListeners.begin(), maListeners.end(), pListener ); + if ( it != maListeners.end() ) + { + maListeners.erase( it ); + mxConfig->removePropertyChangeListener( pListener->maName, this ); + } +} + +void comphelper::ConfigurationListener::dispose() +{ + for (auto const& listener : maListeners) + { + mxConfig->removePropertyChangeListener( listener->maName, this ); + listener->dispose(); + } + maListeners.clear(); + mbDisposed = true; +} + +void SAL_CALL comphelper::ConfigurationListener::disposing(css::lang::EventObject const &) +{ + dispose(); +} + +void SAL_CALL comphelper::ConfigurationListener::propertyChange( + css::beans::PropertyChangeEvent const &rEvt ) +{ + // Code is commonly used inside the SolarMutexGuard + // so to avoid concurrent writes to the property, + // and allow fast, lock-less access, guard here. + // + // Note that we are abusing rtl::Reference here to do acquire/release because, + // unlike osl::Guard, it is tolerant of null pointers, and on some code paths, the + // SolarMutex does not exist. + rtl::Reference<comphelper::SolarMutex> xMutexGuard( comphelper::SolarMutex::get() ); + + assert( rEvt.Source == mxConfig ); + for (auto const& listener : maListeners) + { + if ( listener->maName == rEvt.PropertyName ) + { + // ignore rEvt.NewValue - in theory it could be stale => not set. + css::uno::Any aValue = mxConfig->getPropertyValue( listener->maName ); + listener->setProperty( aValue ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/configurationhelper.cxx b/comphelper/source/misc/configurationhelper.cxx new file mode 100644 index 000000000..f3853baef --- /dev/null +++ b/comphelper/source/misc/configurationhelper.cxx @@ -0,0 +1,170 @@ +/* -*- 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/configurationhelper.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> + + +namespace comphelper{ + + +css::uno::Reference< css::uno::XInterface > ConfigurationHelper::openConfig(const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const OUString& sPackage, + EConfigurationModes eMode ) +{ + css::uno::Reference< css::lang::XMultiServiceFactory > xConfigProvider( + css::configuration::theDefaultProvider::get( rxContext ) ); + + std::vector< css::uno::Any > lParams; + css::beans::PropertyValue aParam ; + + // set root path + aParam.Name = "nodepath"; + aParam.Value <<= sPackage; + lParams.emplace_back(aParam); + + // enable all locales mode + if (eMode & EConfigurationModes::AllLocales) + { + aParam.Name = "locale"; + aParam.Value <<= OUString("*"); + lParams.emplace_back(aParam); + } + + // open it + css::uno::Reference< css::uno::XInterface > xCFG; + + bool bReadOnly(eMode & EConfigurationModes::ReadOnly); + if (bReadOnly) + xCFG = xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", + comphelper::containerToSequence(lParams)); + else + xCFG = xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationUpdateAccess", + comphelper::containerToSequence(lParams)); + + return xCFG; +} + + +css::uno::Any ConfigurationHelper::readRelativeKey(const css::uno::Reference< css::uno::XInterface >& xCFG , + const OUString& sRelPath, + const OUString& sKey ) +{ + css::uno::Reference< css::container::XHierarchicalNameAccess > xAccess(xCFG, css::uno::UNO_QUERY_THROW); + + css::uno::Reference< css::beans::XPropertySet > xProps; + xAccess->getByHierarchicalName(sRelPath) >>= xProps; + if (!xProps.is()) + { + throw css::container::NoSuchElementException( + "The requested path \"" + sRelPath + "\" does not exist."); + } + return xProps->getPropertyValue(sKey); +} + + +void ConfigurationHelper::writeRelativeKey(const css::uno::Reference< css::uno::XInterface >& xCFG , + const OUString& sRelPath, + const OUString& sKey , + const css::uno::Any& aValue ) +{ + css::uno::Reference< css::container::XHierarchicalNameAccess > xAccess(xCFG, css::uno::UNO_QUERY_THROW); + + css::uno::Reference< css::beans::XPropertySet > xProps; + xAccess->getByHierarchicalName(sRelPath) >>= xProps; + if (!xProps.is()) + { + throw css::container::NoSuchElementException( + "The requested path \"" + sRelPath + "\" does not exist."); + } + xProps->setPropertyValue(sKey, aValue); +} + + +css::uno::Reference< css::uno::XInterface > ConfigurationHelper::makeSureSetNodeExists(const css::uno::Reference< css::uno::XInterface >& xCFG , + const OUString& sRelPathToSet, + const OUString& sSetNode ) +{ + css::uno::Reference< css::container::XHierarchicalNameAccess > xAccess(xCFG, css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::container::XNameAccess > xSet; + xAccess->getByHierarchicalName(sRelPathToSet) >>= xSet; + if (!xSet.is()) + { + throw css::container::NoSuchElementException( + "The requested path \"" + sRelPathToSet + "\" does not exist." ); + } + + css::uno::Reference< css::uno::XInterface > xNode; + if (xSet->hasByName(sSetNode)) + xSet->getByName(sSetNode) >>= xNode; + else + { + css::uno::Reference< css::lang::XSingleServiceFactory > xNodeFactory(xSet, css::uno::UNO_QUERY_THROW); + xNode = xNodeFactory->createInstance(); + css::uno::Reference< css::container::XNameContainer > xSetReplace(xSet, css::uno::UNO_QUERY_THROW); + xSetReplace->insertByName(sSetNode, css::uno::Any(xNode)); + } + + return xNode; +} + + +css::uno::Any ConfigurationHelper::readDirectKey(const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const OUString& sPackage, + const OUString& sRelPath, + const OUString& sKey , + EConfigurationModes eMode ) +{ + css::uno::Reference< css::uno::XInterface > xCFG = ConfigurationHelper::openConfig(rxContext, sPackage, eMode); + return ConfigurationHelper::readRelativeKey(xCFG, sRelPath, sKey); +} + + +void ConfigurationHelper::writeDirectKey(const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const OUString& sPackage, + const OUString& sRelPath, + const OUString& sKey , + const css::uno::Any& aValue , + EConfigurationModes eMode ) +{ + css::uno::Reference< css::uno::XInterface > xCFG = ConfigurationHelper::openConfig(rxContext, sPackage, eMode); + ConfigurationHelper::writeRelativeKey(xCFG, sRelPath, sKey, aValue); + ConfigurationHelper::flush(xCFG); +} + + +void ConfigurationHelper::flush(const css::uno::Reference< css::uno::XInterface >& xCFG) +{ + css::uno::Reference< css::util::XChangesBatch > xBatch(xCFG, css::uno::UNO_QUERY_THROW); + xBatch->commitChanges(); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/debuggerinfo.cxx b/comphelper/source/misc/debuggerinfo.cxx new file mode 100644 index 000000000..1e7116a55 --- /dev/null +++ b/comphelper/source/misc/debuggerinfo.cxx @@ -0,0 +1,91 @@ +/* -*- 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/. + */ + +#include <comphelper/debuggerinfo.hxx> + +#include <cassert> +#include <cstring> +#include <ctype.h> + +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#elif defined MACOSX +#include <unistd.h> +#include <sys/types.h> +#include <sys/sysctl.h> +#elif defined UNX +#include <unistd.h> +#include <fcntl.h> +#endif + +namespace comphelper +{ +#if defined DBG_UTIL +bool isDebuggerAttached() +{ +#if defined(_WIN32) + return IsDebuggerPresent(); +#elif defined MACOSX + // https://developer.apple.com/library/archive/qa/qa1361/_index.html + int junk; + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0); + assert(junk == 0); + + // We're being debugged if the P_TRACED flag is set. + + return ((info.kp_proc.p_flag & P_TRACED) != 0); +#elif defined LINUX + char buf[4096]; + int fd = open("/proc/self/status", O_RDONLY); + if (fd < 0) + return false; + int size = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (size < 0) + return false; + assert(size < int(sizeof(buf)) - 1); + buf[sizeof(buf) - 1] = '\0'; + // "TracerPid: <pid>" for pid != 0 means something is attached + const char* pos = strstr(buf, "TracerPid:"); + if (pos == nullptr) + return false; + pos += strlen("TracerPid:"); + while (*pos != '\n' && isspace(*pos)) + ++pos; + return *pos != '\n' && *pos != '0'; +#else + return false; // feel free to add your platform +#endif +} +#endif + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/dispatchcommand.cxx b/comphelper/source/misc/dispatchcommand.cxx new file mode 100644 index 000000000..d7b723c72 --- /dev/null +++ b/comphelper/source/misc/dispatchcommand.cxx @@ -0,0 +1,81 @@ +/* -*- 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/dispatchcommand.hxx> +#include <comphelper/processfactory.hxx> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XNotifyingDispatch.hpp> +#include <com/sun/star/util/URL.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +using namespace css; + +namespace comphelper { + +bool dispatchCommand(const OUString& rCommand, const uno::Reference<css::frame::XFrame>& rFrame, const css::uno::Sequence<css::beans::PropertyValue>& rArguments, const uno::Reference<css::frame::XDispatchResultListener>& rListener) +{ + uno::Reference<frame::XDispatchProvider> xDispatchProvider(rFrame, uno::UNO_QUERY); + if (!xDispatchProvider.is()) + return false; + + util::URL aCommandURL; + aCommandURL.Complete = rCommand; + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + uno::Reference<util::XURLTransformer> xParser = util::URLTransformer::create(xContext); + xParser->parseStrict(aCommandURL); + + uno::Reference<frame::XDispatch> xDisp = xDispatchProvider->queryDispatch(aCommandURL, OUString(), 0); + if (!xDisp.is()) + return false; + + // And do the work... + if (rListener.is()) + { + uno::Reference<frame::XNotifyingDispatch> xNotifyingDisp(xDisp, uno::UNO_QUERY); + if (xNotifyingDisp.is()) + { + xNotifyingDisp->dispatchWithNotification(aCommandURL, rArguments, rListener); + return true; + } + } + + xDisp->dispatch(aCommandURL, rArguments); + + return true; +} + +bool dispatchCommand(const OUString& rCommand, const css::uno::Sequence<css::beans::PropertyValue>& rArguments, const uno::Reference<css::frame::XDispatchResultListener>& rListener) +{ + // Target where we will execute the .uno: command + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(xContext); + + uno::Reference<frame::XFrame> xFrame(xDesktop->getActiveFrame()); + if (!xFrame.is()) + xFrame = xDesktop; + + return dispatchCommand(rCommand, xFrame, rArguments, rListener); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/docpasswordhelper.cxx b/comphelper/source/misc/docpasswordhelper.cxx new file mode 100644 index 000000000..5edb3949c --- /dev/null +++ b/comphelper/source/misc/docpasswordhelper.cxx @@ -0,0 +1,741 @@ +/* -*- 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 <config_gpgme.h> + +#include <algorithm> +#include <string_view> + +#include <comphelper/docpasswordhelper.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/hash.hxx> +#include <comphelper/base64.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> + +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <rtl/digest.h> +#include <rtl/random.h> +#include <string.h> + +#if HAVE_FEATURE_GPGME +# include <context.h> +# include <data.h> +# include <decryptionresult.h> +#endif + +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::task::PasswordRequestMode; +using ::com::sun::star::task::PasswordRequestMode_PASSWORD_ENTER; +using ::com::sun::star::task::PasswordRequestMode_PASSWORD_REENTER; +using ::com::sun::star::task::XInteractionHandler; +using ::com::sun::star::task::XInteractionRequest; + +using namespace ::com::sun::star; + +namespace comphelper { + + +static uno::Sequence< sal_Int8 > GeneratePBKDF2Hash( std::u16string_view aPassword, const uno::Sequence< sal_Int8 >& aSalt, sal_Int32 nCount, sal_Int32 nHashLength ) +{ + uno::Sequence< sal_Int8 > aResult; + + if ( !aPassword.empty() && aSalt.hasElements() && nCount && nHashLength ) + { + OString aBytePass = OUStringToOString( aPassword, RTL_TEXTENCODING_UTF8 ); + // FIXME this is subject to the SHA1-bug tdf#114939 - see also + // RequestPassword() in filedlghelper.cxx + aResult.realloc( 16 ); + rtl_digest_PBKDF2( reinterpret_cast < sal_uInt8 * > ( aResult.getArray() ), + aResult.getLength(), + reinterpret_cast < const sal_uInt8 * > ( aBytePass.getStr() ), + aBytePass.getLength(), + reinterpret_cast < const sal_uInt8 * > ( aSalt.getConstArray() ), + aSalt.getLength(), + nCount ); + } + + return aResult; +} + + +IDocPasswordVerifier::~IDocPasswordVerifier() +{ +} + + +uno::Sequence< beans::PropertyValue > DocPasswordHelper::GenerateNewModifyPasswordInfo( std::u16string_view aPassword ) +{ + uno::Sequence< beans::PropertyValue > aResult; + + uno::Sequence< sal_Int8 > aSalt = GenerateRandomByteSequence( 16 ); + sal_Int32 const nPBKDF2IterationCount = 100000; + + uno::Sequence< sal_Int8 > aNewHash = GeneratePBKDF2Hash(aPassword, aSalt, nPBKDF2IterationCount, 16); + if ( aNewHash.hasElements() ) + { + aResult = { comphelper::makePropertyValue("algorithm-name", OUString( "PBKDF2" )), + comphelper::makePropertyValue("salt", aSalt), + comphelper::makePropertyValue("iteration-count", nPBKDF2IterationCount), + comphelper::makePropertyValue("hash", aNewHash) }; + } + + return aResult; +} + + +uno::Sequence<beans::PropertyValue> +DocPasswordHelper::GenerateNewModifyPasswordInfoOOXML(std::u16string_view aPassword) +{ + uno::Sequence<beans::PropertyValue> aResult; + + if (!aPassword.empty()) + { + uno::Sequence<sal_Int8> aSalt = GenerateRandomByteSequence(16); + OUStringBuffer aBuffer(22); + comphelper::Base64::encode(aBuffer, aSalt); + OUString sSalt = aBuffer.makeStringAndClear(); + + sal_Int32 const nIterationCount = 100000; + OUString sAlgorithm("SHA-512"); + + const OUString sHash(GetOoxHashAsBase64(OUString(aPassword), sSalt, nIterationCount, + comphelper::Hash::IterCount::APPEND, sAlgorithm)); + + if (!sHash.isEmpty()) + { + aResult = { comphelper::makePropertyValue("algorithm-name", sAlgorithm), + comphelper::makePropertyValue("salt", sSalt), + comphelper::makePropertyValue("iteration-count", nIterationCount), + comphelper::makePropertyValue("hash", sHash) }; + } + } + + return aResult; +} + + +uno::Sequence< beans::PropertyValue > DocPasswordHelper::ConvertPasswordInfo( const uno::Sequence< beans::PropertyValue >& aInfo ) +{ + uno::Sequence< beans::PropertyValue > aResult; + OUString sAlgorithm, sHash, sSalt, sCount; + sal_Int32 nAlgorithm = 0; + + for ( const auto & prop : aInfo ) + { + if ( prop.Name == "cryptAlgorithmSid" ) + { + prop.Value >>= sAlgorithm; + nAlgorithm = sAlgorithm.toInt32(); + } + else if ( prop.Name == "salt" ) + prop.Value >>= sSalt; + else if ( prop.Name == "cryptSpinCount" ) + prop.Value >>= sCount; + else if ( prop.Name == "hash" ) + prop.Value >>= sHash; + } + + if (nAlgorithm == 1) + sAlgorithm = "MD2"; + else if (nAlgorithm == 2) + sAlgorithm = "MD4"; + else if (nAlgorithm == 3) + sAlgorithm = "MD5"; + else if (nAlgorithm == 4) + sAlgorithm = "SHA-1"; + else if (nAlgorithm == 5) + sAlgorithm = "MAC"; + else if (nAlgorithm == 6) + sAlgorithm = "RIPEMD"; + else if (nAlgorithm == 7) + sAlgorithm = "RIPEMD-160"; + else if (nAlgorithm == 9) + sAlgorithm = "HMAC"; + else if (nAlgorithm == 12) + sAlgorithm = "SHA-256"; + else if (nAlgorithm == 13) + sAlgorithm = "SHA-384"; + else if (nAlgorithm == 14) + sAlgorithm = "SHA-512"; + + if ( !sCount.isEmpty() ) + { + sal_Int32 nCount = sCount.toInt32(); + aResult = { comphelper::makePropertyValue("algorithm-name", sAlgorithm), + comphelper::makePropertyValue("salt", sSalt), + comphelper::makePropertyValue("iteration-count", nCount), + comphelper::makePropertyValue("hash", sHash) }; + } + + return aResult; +} + + +bool DocPasswordHelper::IsModifyPasswordCorrect( std::u16string_view aPassword, const uno::Sequence< beans::PropertyValue >& aInfo ) +{ + bool bResult = false; + if ( !aPassword.empty() && aInfo.hasElements() ) + { + OUString sAlgorithm; + uno::Any aSalt, aHash; + sal_Int32 nCount = 0; + + for ( const auto & prop : aInfo ) + { + if ( prop.Name == "algorithm-name" ) + prop.Value >>= sAlgorithm; + else if ( prop.Name == "salt" ) + aSalt = prop.Value; + else if ( prop.Name == "iteration-count" ) + prop.Value >>= nCount; + else if ( prop.Name == "hash" ) + aHash = prop.Value; + } + + if ( sAlgorithm == "PBKDF2" ) + { + uno::Sequence<sal_Int8> aIntSalt, aIntHash; + aSalt >>= aIntSalt; + aHash >>= aIntHash; + if (aIntSalt.hasElements() && nCount > 0 && aIntHash.hasElements()) + { + uno::Sequence<sal_Int8> aNewHash + = GeneratePBKDF2Hash(aPassword, aIntSalt, nCount, aIntHash.getLength()); + for (sal_Int32 nInd = 0; nInd < aNewHash.getLength() && nInd < aIntHash.getLength() + && aNewHash[nInd] == aIntHash[nInd]; + nInd++) + { + if (nInd == aNewHash.getLength() - 1 && nInd == aIntHash.getLength() - 1) + bResult = true; + } + } + } + else if (nCount > 0) + { + OUString sSalt, sHash; + aSalt >>= sSalt; + aHash >>= sHash; + if (!sSalt.isEmpty() && !sHash.isEmpty()) + { + const OUString aNewHash(GetOoxHashAsBase64(OUString(aPassword), sSalt, nCount, + comphelper::Hash::IterCount::APPEND, + sAlgorithm)); + if (!aNewHash.isEmpty()) + bResult = aNewHash == sHash; + } + } + } + + return bResult; +} + + +sal_uInt32 DocPasswordHelper::GetWordHashAsUINT32( + std::u16string_view aUString ) +{ + static const sal_uInt16 pInitialCode[] = { + 0xE1F0, // 1 + 0x1D0F, // 2 + 0xCC9C, // 3 + 0x84C0, // 4 + 0x110C, // 5 + 0x0E10, // 6 + 0xF1CE, // 7 + 0x313E, // 8 + 0x1872, // 9 + 0xE139, // 10 + 0xD40F, // 11 + 0x84F9, // 12 + 0x280C, // 13 + 0xA96A, // 14 + 0x4EC3 // 15 + }; + + static const sal_uInt16 pEncryptionMatrix[15][7] = { + { 0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09}, // last-14 + { 0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF}, // last-13 + { 0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0}, // last-12 + { 0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40}, // last-11 + { 0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5}, // last-10 + { 0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A}, // last-9 + { 0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9}, // last-8 + { 0x47D3, 0x8FA6, 0x8FA6, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0}, // last-7 + { 0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC}, // last-6 + { 0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10}, // last-5 + { 0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168}, // last-4 + { 0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C}, // last-3 + { 0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD}, // last-2 + { 0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC}, // last-1 + { 0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4} // last + }; + + sal_uInt32 nResult = 0; + size_t nLen = aUString.size(); + + if ( nLen ) + { + if ( nLen > 15 ) + nLen = 15; + + sal_uInt16 nHighResult = pInitialCode[nLen - 1]; + sal_uInt16 nLowResult = 0; + + for ( size_t nInd = 0; nInd < nLen; nInd++ ) + { + // NO Encoding during conversion! + // The specification says that the low byte should be used in case it is not NULL + char nHighChar = static_cast<char>( aUString[nInd] >> 8 ); + char nLowChar = static_cast<char>( aUString[nInd] & 0xFF ); + char nChar = nLowChar ? nLowChar : nHighChar; + + for ( int nMatrixInd = 0; nMatrixInd < 7; ++nMatrixInd ) + { + if ( ( nChar & ( 1 << nMatrixInd ) ) != 0 ) + nHighResult = nHighResult ^ pEncryptionMatrix[15 - nLen + nInd][nMatrixInd]; + } + + nLowResult = ( ( ( nLowResult >> 14 ) & 0x0001 ) | ( ( nLowResult << 1 ) & 0x7FFF ) ) ^ nChar; + } + + nLowResult = static_cast<sal_uInt16>( ( ( ( nLowResult >> 14 ) & 0x001 ) | ( ( nLowResult << 1 ) & 0x7FF ) ) ^ nLen ^ 0xCE4B ); + + nResult = ( nHighResult << 16 ) | nLowResult; + } + + return nResult; +} + + +sal_uInt16 DocPasswordHelper::GetXLHashAsUINT16( + std::u16string_view aUString, + rtl_TextEncoding nEnc ) +{ + sal_uInt16 nResult = 0; + + OString aString = OUStringToOString( aUString, nEnc ); + + if ( !aString.isEmpty() && aString.getLength() <= SAL_MAX_UINT16 ) + { + for ( sal_Int32 nInd = aString.getLength() - 1; nInd >= 0; nInd-- ) + { + nResult = ( ( nResult >> 14 ) & 0x01 ) | ( ( nResult << 1 ) & 0x7FFF ); + nResult ^= aString[nInd]; + } + + nResult = ( ( nResult >> 14 ) & 0x01 ) | ( ( nResult << 1 ) & 0x7FFF ); + nResult ^= ( 0x8000 | ( 'N' << 8 ) | 'K' ); + nResult ^= aString.getLength(); + } + + return nResult; +} + + +Sequence< sal_Int8 > DocPasswordHelper::GetXLHashAsSequence( + std::u16string_view aUString ) +{ + sal_uInt16 nHash = GetXLHashAsUINT16( aUString ); + return {sal_Int8(nHash >> 8), sal_Int8(nHash & 0xFF)}; +} + + +std::vector<unsigned char> DocPasswordHelper::GetOoxHashAsVector( + const OUString& rPassword, + const std::vector<unsigned char>& rSaltValue, + sal_uInt32 nSpinCount, + comphelper::Hash::IterCount eIterCount, + std::u16string_view rAlgorithmName) +{ + comphelper::HashType eType; + if (rAlgorithmName == u"SHA-512" || rAlgorithmName == u"SHA512") + eType = comphelper::HashType::SHA512; + else if (rAlgorithmName == u"SHA-256" || rAlgorithmName == u"SHA256") + eType = comphelper::HashType::SHA256; + else if (rAlgorithmName == u"SHA-1" || rAlgorithmName == u"SHA1") // "SHA1" might be in the wild + eType = comphelper::HashType::SHA1; + else if (rAlgorithmName == u"MD5") + eType = comphelper::HashType::MD5; + else + return std::vector<unsigned char>(); + + return comphelper::Hash::calculateHash( rPassword, rSaltValue, nSpinCount, eIterCount, eType); +} + + +css::uno::Sequence<sal_Int8> DocPasswordHelper::GetOoxHashAsSequence( + const OUString& rPassword, + std::u16string_view rSaltValue, + sal_uInt32 nSpinCount, + comphelper::Hash::IterCount eIterCount, + std::u16string_view rAlgorithmName) +{ + std::vector<unsigned char> aSaltVec; + if (!rSaltValue.empty()) + { + css::uno::Sequence<sal_Int8> aSaltSeq; + comphelper::Base64::decode( aSaltSeq, rSaltValue); + aSaltVec = comphelper::sequenceToContainer<std::vector<unsigned char>>( aSaltSeq); + } + + std::vector<unsigned char> hash( GetOoxHashAsVector( rPassword, aSaltVec, nSpinCount, eIterCount, rAlgorithmName)); + + return comphelper::containerToSequence<sal_Int8>( hash); +} + +OUString DocPasswordHelper::GetOoxHashAsBase64( + const OUString& rPassword, + std::u16string_view rSaltValue, + sal_uInt32 nSpinCount, + comphelper::Hash::IterCount eIterCount, + std::u16string_view rAlgorithmName) +{ + css::uno::Sequence<sal_Int8> aSeq( GetOoxHashAsSequence( rPassword, rSaltValue, nSpinCount, + eIterCount, rAlgorithmName)); + + OUStringBuffer aBuf((aSeq.getLength()+2)/3*4); + comphelper::Base64::encode( aBuf, aSeq); + return aBuf.makeStringAndClear(); +} + + +/*static*/ uno::Sequence< sal_Int8 > DocPasswordHelper::GenerateRandomByteSequence( sal_Int32 nLength ) +{ + uno::Sequence< sal_Int8 > aResult( nLength ); + + rtlRandomPool aRandomPool = rtl_random_createPool (); + rtl_random_getBytes ( aRandomPool, aResult.getArray(), nLength ); + rtl_random_destroyPool ( aRandomPool ); + + return aResult; +} + + +/*static*/ uno::Sequence< sal_Int8 > DocPasswordHelper::GenerateStd97Key( std::u16string_view aPassword, const uno::Sequence< sal_Int8 >& aDocId ) +{ + uno::Sequence< sal_Int8 > aResultKey; + if ( !aPassword.empty() && aDocId.getLength() == 16 ) + { + sal_uInt16 pPassData[16] = {}; + + sal_Int32 nPassLen = std::min< sal_Int32 >( aPassword.size(), 15 ); + memcpy( pPassData, aPassword.data(), nPassLen * sizeof(pPassData[0]) ); + + aResultKey = GenerateStd97Key( pPassData, aDocId ); + } + + return aResultKey; +} + + +/*static*/ uno::Sequence< sal_Int8 > DocPasswordHelper::GenerateStd97Key( const sal_uInt16 pPassData[16], const uno::Sequence< sal_Int8 >& aDocId ) +{ + uno::Sequence< sal_Int8 > aResultKey; + + if ( aDocId.getLength() == 16 ) + aResultKey = GenerateStd97Key(pPassData, reinterpret_cast<const sal_uInt8*>(aDocId.getConstArray())); + + return aResultKey; +} + + +/*static*/ uno::Sequence< sal_Int8 > DocPasswordHelper::GenerateStd97Key( const sal_uInt16 pPassData[16], const sal_uInt8 pDocId[16] ) +{ + uno::Sequence< sal_Int8 > aResultKey; + if ( pPassData[0] ) + { + sal_uInt8 pKeyData[64] = {}; + + sal_Int32 nInd = 0; + + // Fill PassData into KeyData. + for ( nInd = 0; nInd < 16 && pPassData[nInd]; nInd++) + { + pKeyData[2*nInd] = sal::static_int_cast< sal_uInt8 >( (pPassData[nInd] >> 0) & 0xff ); + pKeyData[2*nInd + 1] = sal::static_int_cast< sal_uInt8 >( (pPassData[nInd] >> 8) & 0xff ); + } + + pKeyData[2*nInd] = 0x80; + pKeyData[56] = sal::static_int_cast< sal_uInt8 >( nInd << 4 ); + + // Fill raw digest of KeyData into KeyData. + rtlDigest hDigest = rtl_digest_create ( rtl_Digest_AlgorithmMD5 ); + (void)rtl_digest_updateMD5 ( + hDigest, pKeyData, sizeof(pKeyData)); + (void)rtl_digest_rawMD5 ( + hDigest, pKeyData, RTL_DIGEST_LENGTH_MD5); + + // Update digest with KeyData and Unique. + for ( nInd = 0; nInd < 16; nInd++ ) + { + rtl_digest_updateMD5( hDigest, pKeyData, 5 ); + rtl_digest_updateMD5( hDigest, pDocId, 16 ); + } + + // Update digest with padding. + pKeyData[16] = 0x80; + memset( pKeyData + 17, 0, sizeof(pKeyData) - 17 ); + pKeyData[56] = 0x80; + pKeyData[57] = 0x0a; + + rtl_digest_updateMD5( hDigest, &(pKeyData[16]), sizeof(pKeyData) - 16 ); + + // Fill raw digest of above updates + aResultKey.realloc( RTL_DIGEST_LENGTH_MD5 ); + rtl_digest_rawMD5 ( hDigest, reinterpret_cast<sal_uInt8*>(aResultKey.getArray()), aResultKey.getLength() ); + + // Erase KeyData array and leave. + rtl_secureZeroMemory (pKeyData, sizeof(pKeyData)); + + rtl_digest_destroy(hDigest); + } + + return aResultKey; +} + + +/*static*/ css::uno::Sequence< css::beans::NamedValue > DocPasswordHelper::requestAndVerifyDocPassword( + IDocPasswordVerifier& rVerifier, + const css::uno::Sequence< css::beans::NamedValue >& rMediaEncData, + const OUString& rMediaPassword, + const Reference< XInteractionHandler >& rxInteractHandler, + const OUString& rDocumentUrl, + DocPasswordRequestType eRequestType, + const std::vector< OUString >* pDefaultPasswords, + bool* pbIsDefaultPassword ) +{ + css::uno::Sequence< css::beans::NamedValue > aEncData; + OUString aPassword; + DocPasswordVerifierResult eResult = DocPasswordVerifierResult::WrongPassword; + + sal_Int32 nMediaEncDataCount = rMediaEncData.getLength(); + + // tdf#93389: if the document is being restored from autorecovery, we need to add encryption + // data also for real document type. + // TODO: get real filter name here (from CheckPasswd_Impl), to only add necessary data + bool bForSalvage = false; + if (nMediaEncDataCount) + { + for (auto& val : rMediaEncData) + { + if (val.Name == "ForSalvage") + { + --nMediaEncDataCount; // don't consider this element below + val.Value >>= bForSalvage; + break; + } + } + } + + // first, try provided default passwords + if( pbIsDefaultPassword ) + *pbIsDefaultPassword = false; + if( pDefaultPasswords ) + { + for( const auto& rPassword : *pDefaultPasswords ) + { + OSL_ENSURE( !rPassword.isEmpty(), "DocPasswordHelper::requestAndVerifyDocPassword - unexpected empty default password" ); + if( !rPassword.isEmpty() ) + { + eResult = rVerifier.verifyPassword( rPassword, aEncData ); + if (eResult == DocPasswordVerifierResult::OK) + { + aPassword = rPassword; + if (pbIsDefaultPassword) + *pbIsDefaultPassword = true; + } + if( eResult != DocPasswordVerifierResult::WrongPassword ) + break; + } + } + } + + // try media encryption data (skip, if result is OK or ABORT) + if( eResult == DocPasswordVerifierResult::WrongPassword ) + { + if (nMediaEncDataCount) + { + eResult = rVerifier.verifyEncryptionData( rMediaEncData ); + if( eResult == DocPasswordVerifierResult::OK ) + aEncData = rMediaEncData; + } + } + + // try media password (skip, if result is OK or ABORT) + if( eResult == DocPasswordVerifierResult::WrongPassword ) + { + if( !rMediaPassword.isEmpty() ) + { + eResult = rVerifier.verifyPassword( rMediaPassword, aEncData ); + if (eResult == DocPasswordVerifierResult::OK) + aPassword = rMediaPassword; + } + } + + // request a password (skip, if result is OK or ABORT) + if( (eResult == DocPasswordVerifierResult::WrongPassword) && rxInteractHandler.is() ) try + { + PasswordRequestMode eRequestMode = PasswordRequestMode_PASSWORD_ENTER; + while( eResult == DocPasswordVerifierResult::WrongPassword ) + { + rtl::Reference<DocPasswordRequest> pRequest = new DocPasswordRequest( eRequestType, eRequestMode, rDocumentUrl ); + rxInteractHandler->handle( pRequest ); + if( pRequest->isPassword() ) + { + if( !pRequest->getPassword().isEmpty() ) + eResult = rVerifier.verifyPassword( pRequest->getPassword(), aEncData ); + if (eResult == DocPasswordVerifierResult::OK) + aPassword = pRequest->getPassword(); + } + else + { + eResult = DocPasswordVerifierResult::Abort; + } + eRequestMode = PasswordRequestMode_PASSWORD_REENTER; + } + } + catch( Exception& ) + { + } + + if (eResult == DocPasswordVerifierResult::OK && !aPassword.isEmpty()) + { + if (std::find_if(std::cbegin(aEncData), std::cend(aEncData), + [](const css::beans::NamedValue& val) { + return val.Name == PACKAGE_ENCRYPTIONDATA_SHA256UTF8; + }) + == std::cend(aEncData)) + { + // tdf#118639: We need ODF encryption data for autorecovery, where password + // will already be unavailable, so generate and append it here + aEncData = comphelper::concatSequences( + aEncData, OStorageHelper::CreatePackageEncryptionData(aPassword)); + } + + if (bForSalvage) + { + // TODO: add individual methods for different target filter, and only call what's needed + + // 1. Prepare binary MS formats encryption data + auto aUniqueID = GenerateRandomByteSequence(16); + auto aEnc97Key = GenerateStd97Key(aPassword, aUniqueID); + // 2. Add MS binary and OOXML encryption data to result + aEncData = comphelper::concatSequences( + aEncData, std::initializer_list<beans::NamedValue>{ + { "STD97EncryptionKey", css::uno::Any(aEnc97Key) }, + { "STD97UniqueID", css::uno::Any(aUniqueID) }, + { "OOXPassword", css::uno::Any(aPassword) }, + }); + } + } + + return (eResult == DocPasswordVerifierResult::OK) ? aEncData : uno::Sequence< beans::NamedValue >(); +} + +/*static*/ uno::Sequence< css::beans::NamedValue > + DocPasswordHelper::decryptGpgSession( + const uno::Sequence< uno::Sequence< beans::NamedValue > >& rGpgProperties ) +{ +#if HAVE_FEATURE_GPGME + if ( !rGpgProperties.hasElements() ) + return uno::Sequence< beans::NamedValue >(); + + uno::Sequence< beans::NamedValue > aEncryptionData; + std::unique_ptr<GpgME::Context> ctx; + GpgME::initializeLibrary(); + GpgME::Error err = GpgME::checkEngine(GpgME::OpenPGP); + if (err) + throw uno::RuntimeException("The GpgME library failed to initialize for the OpenPGP protocol."); + + ctx.reset( GpgME::Context::createForProtocol(GpgME::OpenPGP) ); + if (ctx == nullptr) + throw uno::RuntimeException("The GpgME library failed to initialize for the OpenPGP protocol."); + ctx->setArmor(false); + + const uno::Sequence < beans::NamedValue > *pSequence = rGpgProperties.getConstArray(); + const sal_Int32 nLength = rGpgProperties.getLength(); + for ( sal_Int32 i = 0; i < nLength ; i++, pSequence++ ) + { + const beans::NamedValue *pValues = pSequence->getConstArray(); + if ( pSequence->getLength() == 3 ) + { + // take CipherValue and try to decrypt that - stop after + // the first successful decryption + + // ctx is setup now, let's decrypt the lot! + uno::Sequence < sal_Int8 > aVector; + pValues[2].Value >>= aVector; + + GpgME::Data cipher( + reinterpret_cast<const char*>(aVector.getConstArray()), + size_t(aVector.getLength()), false); + GpgME::Data plain; + + GpgME::DecryptionResult crypt_res = ctx->decrypt( + cipher, plain); + + // NO_SECKEY -> skip + // BAD_PASSPHRASE -> retry? + + off_t result = plain.seek(0,SEEK_SET); + (void) result; + assert(result == 0); + int len=0, curr=0; char buf; + while( (curr=plain.read(&buf, 1)) ) + len += curr; + + if(crypt_res.error() || !len) + continue; // can't use this key, take next one + + uno::Sequence < sal_Int8 > aKeyValue(len); + result = plain.seek(0,SEEK_SET); + assert(result == 0); + if( plain.read(aKeyValue.getArray(), len) != len ) + throw uno::RuntimeException("The GpgME library failed to read the encrypted value."); + + SAL_INFO("comphelper.crypto", "Extracted gpg session key of length: " << len); + + aEncryptionData = { { PACKAGE_ENCRYPTIONDATA_SHA256UTF8, uno::Any(aKeyValue) } }; + break; + } + } + + if ( aEncryptionData.hasElements() ) + { + uno::Sequence< beans::NamedValue > aContainer{ + { "GpgInfos", uno::Any(rGpgProperties) }, { "EncryptionKey", uno::Any(aEncryptionData) } + }; + + return aContainer; + } +#else + (void)rGpgProperties; +#endif + return uno::Sequence< beans::NamedValue >(); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/docpasswordrequest.cxx b/comphelper/source/misc/docpasswordrequest.cxx new file mode 100644 index 000000000..6f644336e --- /dev/null +++ b/comphelper/source/misc/docpasswordrequest.cxx @@ -0,0 +1,179 @@ +/* -*- 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/docpasswordrequest.hxx> +#include <com/sun/star/task/DocumentMSPasswordRequest2.hpp> +#include <com/sun/star/task/DocumentPasswordRequest2.hpp> +#include <com/sun/star/task/PasswordRequest.hpp> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/task/XInteractionPassword2.hpp> +#include <cppuhelper/implbase.hxx> + +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::XInterface; +using ::com::sun::star::task::InteractionClassification_QUERY; +using ::com::sun::star::task::DocumentMSPasswordRequest2; +using ::com::sun::star::task::DocumentPasswordRequest2; +using ::com::sun::star::task::PasswordRequest; +using ::com::sun::star::task::PasswordRequestMode; +using ::com::sun::star::task::XInteractionAbort; +using ::com::sun::star::task::XInteractionContinuation; +using ::com::sun::star::task::XInteractionPassword2; + +namespace comphelper { + +namespace { + +class AbortContinuation : public ::cppu::WeakImplHelper< XInteractionAbort > +{ +public: + virtual void SAL_CALL select() override {} +}; + +} + +class PasswordContinuation : public ::cppu::WeakImplHelper< XInteractionPassword2 > +{ +public: + explicit PasswordContinuation() : mbReadOnly( false ), mbSelected( false ) {} + + bool isSelected() const { return mbSelected; } + + virtual void SAL_CALL select() override { mbSelected = true; } + + virtual void SAL_CALL setPassword( const OUString& rPass ) override { maPassword = rPass; } + virtual OUString SAL_CALL getPassword() override { return maPassword; } + + virtual void SAL_CALL setPasswordToModify( const OUString& rPass ) override { maModifyPassword = rPass; } + virtual OUString SAL_CALL getPasswordToModify() override { return maModifyPassword; } + + virtual void SAL_CALL setRecommendReadOnly( sal_Bool bReadOnly ) override { mbReadOnly = bReadOnly; } + virtual sal_Bool SAL_CALL getRecommendReadOnly() override { return mbReadOnly; } + +private: + OUString maPassword; + OUString maModifyPassword; + bool mbReadOnly; + bool mbSelected; +}; + + +SimplePasswordRequest::SimplePasswordRequest() +{ + PasswordRequest aRequest( OUString(), Reference< XInterface >(), + InteractionClassification_QUERY, css::task::PasswordRequestMode_PASSWORD_CREATE ); + maRequest <<= aRequest; + + mxAbort = new AbortContinuation; + mxPassword = new PasswordContinuation; +} + +SimplePasswordRequest::~SimplePasswordRequest() +{ +} + +bool SimplePasswordRequest::isPassword() const +{ + return mxPassword->isSelected(); +} + +OUString SimplePasswordRequest::getPassword() const +{ + return mxPassword->getPassword(); +} + +Any SAL_CALL SimplePasswordRequest::getRequest() +{ + return maRequest; +} + +Sequence< Reference< XInteractionContinuation > > SAL_CALL SimplePasswordRequest::getContinuations() +{ + return { mxAbort, mxPassword }; +} + + +DocPasswordRequest::DocPasswordRequest( DocPasswordRequestType eType, + PasswordRequestMode eMode, const OUString& rDocumentUrl, bool bPasswordToModify ) +{ + switch( eType ) + { + case DocPasswordRequestType::Standard: + { + DocumentPasswordRequest2 aRequest( OUString(), Reference< XInterface >(), + InteractionClassification_QUERY, eMode, rDocumentUrl, bPasswordToModify ); + maRequest <<= aRequest; + } + break; + case DocPasswordRequestType::MS: + { + DocumentMSPasswordRequest2 aRequest( OUString(), Reference< XInterface >(), + InteractionClassification_QUERY, eMode, rDocumentUrl, bPasswordToModify ); + maRequest <<= aRequest; + } + break; + /* no 'default', so compilers will complain about missing + implementation of a new enum value. */ + } + + mxAbort = new AbortContinuation; + mxPassword = new PasswordContinuation; +} + +DocPasswordRequest::~DocPasswordRequest() +{ +} + +bool DocPasswordRequest::isPassword() const +{ + return mxPassword->isSelected(); +} + +OUString DocPasswordRequest::getPassword() const +{ + return mxPassword->getPassword(); +} + +OUString DocPasswordRequest::getPasswordToModify() const +{ + return mxPassword->getPasswordToModify(); +} + +bool DocPasswordRequest::getRecommendReadOnly() const +{ + return mxPassword->getRecommendReadOnly(); +} + +Any SAL_CALL DocPasswordRequest::getRequest() +{ + return maRequest; +} + +Sequence< Reference< XInteractionContinuation > > SAL_CALL DocPasswordRequest::getContinuations() +{ + return { mxAbort, mxPassword }; +} + + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/documentinfo.cxx b/comphelper/source/misc/documentinfo.cxx new file mode 100644 index 000000000..21425524e --- /dev/null +++ b/comphelper/source/misc/documentinfo.cxx @@ -0,0 +1,177 @@ +/* -*- 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/documentinfo.hxx> +#include <comphelper/namedvaluecollection.hxx> + +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/frame/XTitle.hpp> + +#include <cppuhelper/exc_hlp.hxx> + +#include <sal/log.hxx> + +namespace comphelper { + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::frame::XModel; + using ::com::sun::star::frame::XTitle; + using ::com::sun::star::frame::XController; + using ::com::sun::star::document::XDocumentPropertiesSupplier; + using ::com::sun::star::document::XDocumentProperties; + using ::com::sun::star::frame::XStorable; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::frame::XFrame; + + namespace + { + OUString lcl_getTitle( const Reference< XInterface >& _rxComponent ) + { + Reference< XTitle > xTitle( _rxComponent, UNO_QUERY ); + if ( xTitle.is() ) + return xTitle->getTitle(); + return OUString(); + } + } + + OUString DocumentInfo::getDocumentTitle( const Reference< XModel >& _rxDocument ) + { + OUString sTitle; + + if ( !_rxDocument.is() ) + return sTitle; + + try + { + // 1. ask the model and the controller for their XTitle::getTitle + sTitle = lcl_getTitle( _rxDocument ); + if ( !sTitle.isEmpty() ) + return sTitle; + + Reference< XController > xController( _rxDocument->getCurrentController() ); + sTitle = lcl_getTitle( xController ); + if ( !sTitle.isEmpty() ) + return sTitle; + + // work around a problem with embedded objects, which sometimes return + // private:object as URL + OUString sDocURL = _rxDocument->getURL(); + if ( sDocURL.startsWithIgnoreAsciiCase( "private:" ) ) + sDocURL.clear(); + + // 2. if the document is not saved, yet, check the frame title + if ( sDocURL.isEmpty() ) + { + Reference< XFrame > xFrame; + if ( xController.is() ) + xFrame.set( xController->getFrame() ); + sTitle = lcl_getTitle( xFrame ); + if ( !sTitle.isEmpty() ) + return sTitle; + } + + // 3. try the UNO XDocumentProperties + Reference< XDocumentPropertiesSupplier > xDPS( _rxDocument, UNO_QUERY ); + if ( xDPS.is() ) + { + Reference< XDocumentProperties > xDocProps ( + xDPS->getDocumentProperties(), css::uno::UNO_SET_THROW ); + sTitle = xDocProps->getTitle(); + if ( !sTitle.isEmpty() ) + return sTitle; + } + + // 4. try model arguments + sTitle = NamedValueCollection::getOrDefault( _rxDocument->getArgs(), u"Title", sTitle ); + if ( !sTitle.isEmpty() ) + return sTitle; + + // 5. try the last segment of the document URL + // this formerly was an INetURLObject::getName( LAST_SEGMENT, true, DecodeMechanism::WithCharset ), + // but since we moved this code to comphelper, we do not have access to an INetURLObject anymore + // This heuristics here should be sufficient - finally, we will get a UNO title API in a not + // too distant future (hopefully), then this complete class is superfluous) + if ( sDocURL.isEmpty() ) + { + Reference< XStorable > xDocStorable( _rxDocument, UNO_QUERY_THROW ); + sDocURL = xDocStorable->getLocation(); + } + sal_Int32 nLastSepPos = sDocURL.lastIndexOf( '/' ); + if ( ( nLastSepPos != -1 ) && ( nLastSepPos == sDocURL.getLength() - 1 ) ) + { + sDocURL = sDocURL.copy( 0, nLastSepPos ); + nLastSepPos = sDocURL.lastIndexOf( '/' ); + } + sTitle = sDocURL.copy( nLastSepPos + 1 ); + + if ( !sTitle.isEmpty() ) + return sTitle; + + // 5. + // <-- #i88104# (05-16-08) TKR: use the new XTitle Interface to get the Title --> + + Reference< XTitle > xTitle( _rxDocument, UNO_QUERY ); + if ( xTitle.is() ) + { + if ( !xTitle->getTitle().isEmpty() ) + return xTitle->getTitle(); + } + } + catch ( const Exception& ) + { + // Cannot use tools::exceptionToString here, because the tools module depends on the comphelper module + css::uno::Any caught( ::cppu::getCaughtException() ); + css::uno::Exception exception; + caught >>= exception; + SAL_WARN( "comphelper", "caught an exception!\ntype : " << caught.getValueTypeName() + << "\nmessage: " << exception + << "\nin function:\n" << __func__); + } + + return sTitle; + } + + void DocumentInfo::notifyMacroEventRead(const css::uno::Reference<css::frame::XModel>& rModel) + { + if (!rModel.is()) + return; + + // like BreakMacroSignature of XMLScriptContext use XModel::attachResource + // to propagate this notification + css::uno::Sequence<css::beans::PropertyValue> aMedDescr = rModel->getArgs(); + sal_Int32 nNewLen = aMedDescr.getLength() + 1; + aMedDescr.realloc(nNewLen); + auto pMedDescr = aMedDescr.getArray(); + pMedDescr[nNewLen-1].Name = "MacroEventRead"; + pMedDescr[nNewLen-1].Value <<= true; + rModel->attachResource(rModel->getURL(), aMedDescr); + } + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/evtlistenerhlp.cxx b/comphelper/source/misc/evtlistenerhlp.cxx new file mode 100644 index 000000000..2eac31580 --- /dev/null +++ b/comphelper/source/misc/evtlistenerhlp.cxx @@ -0,0 +1,37 @@ +/* -*- 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/evtlistenerhlp.hxx> + +namespace comphelper +{ + OEventListenerHelper::OEventListenerHelper(const css::uno::Reference< css::lang::XEventListener>& + _rxListener) : m_xListener(_rxListener) + { + } + void SAL_CALL OEventListenerHelper::disposing( const css::lang::EventObject& Source ) + { + css::uno::Reference< css::lang::XEventListener> xRef = m_xListener; + if(xRef.is()) + xRef->disposing(Source); + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/evtmethodhelper.cxx b/comphelper/source/misc/evtmethodhelper.cxx new file mode 100644 index 000000000..5f60d92d6 --- /dev/null +++ b/comphelper/source/misc/evtmethodhelper.cxx @@ -0,0 +1,59 @@ +/* -*- 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/evtmethodhelper.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Type; + +namespace comphelper +{ + + Sequence< OUString> getEventMethodsForType(const Type& type) + { + typelib_InterfaceTypeDescription *pType=nullptr; + type.getDescription(reinterpret_cast<typelib_TypeDescription**>(&pType)); + + if(!pType) + return Sequence< OUString>(); + + Sequence< OUString> aNames(pType->nMembers); + OUString* pNames = aNames.getArray(); + for(sal_Int32 i=0;i<pType->nMembers;i++,++pNames) + { + // the description reference + typelib_TypeDescriptionReference* pMemberDescriptionReference = pType->ppMembers[i]; + // the description for the reference + typelib_TypeDescription* pMemberDescription = nullptr; + typelib_typedescriptionreference_getDescription(&pMemberDescription, pMemberDescriptionReference); + if (pMemberDescription) + { + typelib_InterfaceMemberTypeDescription* pRealMemberDescription = + reinterpret_cast<typelib_InterfaceMemberTypeDescription*>(pMemberDescription); + *pNames = pRealMemberDescription->pMemberName; + } + } + typelib_typedescription_release( &pType->aBase ); + return aNames; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/fileurl.cxx b/comphelper/source/misc/fileurl.cxx new file mode 100644 index 000000000..4f361ab4b --- /dev/null +++ b/comphelper/source/misc/fileurl.cxx @@ -0,0 +1,27 @@ +/* -*- 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 <comphelper/fileurl.hxx> +#include <rtl/ustring.hxx> + +bool comphelper::isFileUrl(OUString const& url) { return url.startsWithIgnoreAsciiCase("file:"); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/getexpandeduri.cxx b/comphelper/source/misc/getexpandeduri.cxx new file mode 100644 index 000000000..853e5dbd3 --- /dev/null +++ b/comphelper/source/misc/getexpandeduri.cxx @@ -0,0 +1,32 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/uri/XVndSunStarExpandUrlReference.hpp> +#include <com/sun/star/util/theMacroExpander.hpp> +#include <comphelper/getexpandeduri.hxx> +#include <rtl/ustring.hxx> + +namespace com::sun::star::uno { class XComponentContext; } + +OUString comphelper::getExpandedUri( + css::uno::Reference<css::uno::XComponentContext> const & context, + OUString const & uri) +{ + css::uno::Reference<css::uri::XVndSunStarExpandUrlReference> ref( + css::uri::UriReferenceFactory::create(context)->parse(uri), + css::uno::UNO_QUERY); + return ref.is() + ? ref->expand(css::util::theMacroExpander::get(context)) : uri; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/graphicmimetype.cxx b/comphelper/source/misc/graphicmimetype.cxx new file mode 100644 index 000000000..bfa7e5c6a --- /dev/null +++ b/comphelper/source/misc/graphicmimetype.cxx @@ -0,0 +1,171 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/graphicmimetype.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/graphic/XGraphicProvider.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/uno/Reference.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> + +using namespace css; +using namespace css::beans; +using namespace css::graphic; +using namespace css::io; +using namespace css::uno; + +namespace comphelper +{ +OUString GraphicMimeTypeHelper::GetMimeTypeForExtension(std::string_view rExt) +{ + struct XMLGraphicMimeTypeMapper + { + const char* pExt; + const char* pMimeType; + }; + + static const XMLGraphicMimeTypeMapper aMapper[] + = { { "gif", "image/gif" }, { "png", "image/png" }, { "jpg", "image/jpeg" }, + { "tif", "image/tiff" }, { "svg", "image/svg+xml" }, { "pdf", "application/pdf" }, + { "wmf", "image/x-wmf" }, { "emf", "image/x-emf" }, { "eps", "image/x-eps" }, + { "bmp", "image/bmp" }, { "pct", "image/x-pict" }, { "svm", "image/x-svm" } }; + + OUString aMimeType; + + tools::Long const nCount = std::size(aMapper); + for (tools::Long i = 0; (i < nCount) && aMimeType.isEmpty(); ++i) + { + if (rExt == aMapper[i].pExt) + aMimeType = OUString(aMapper[i].pMimeType, strlen(aMapper[i].pMimeType), + RTL_TEXTENCODING_ASCII_US); + } + + return aMimeType; +} + +OUString GraphicMimeTypeHelper::GetMimeTypeForXGraphic(const Reference<XGraphic>& xGraphic) +{ + OUString aSourceMimeType; + Reference<XPropertySet> const xGraphicPropertySet(xGraphic, UNO_QUERY); + if (xGraphicPropertySet.is() && // it's null if it's an external link + (xGraphicPropertySet->getPropertyValue("MimeType") >>= aSourceMimeType)) + { + return aSourceMimeType; + } + return ""; +} + +OUString +GraphicMimeTypeHelper::GetMimeTypeForImageStream(const Reference<XInputStream>& xInputStream) +{ + // Create the graphic to retrieve the mimetype from it + Reference<XGraphicProvider> xProvider + = css::graphic::GraphicProvider::create(comphelper::getProcessComponentContext()); + Sequence<PropertyValue> aMediaProperties{ comphelper::makePropertyValue("InputStream", + xInputStream) }; + Reference<XGraphic> xGraphic(xProvider->queryGraphic(aMediaProperties)); + + return GetMimeTypeForXGraphic(xGraphic); +} + +OUString GraphicMimeTypeHelper::GetMimeTypeForConvertDataFormat(ConvertDataFormat convertDataFormat) +{ + switch (convertDataFormat) + { + case ConvertDataFormat::BMP: + return "image/bmp"; + case ConvertDataFormat::GIF: + return "image/gif"; + case ConvertDataFormat::JPG: + return "image/jpeg"; + case ConvertDataFormat::PCT: + return "image/x-pict"; + case ConvertDataFormat::PNG: + return "image/png"; + case ConvertDataFormat::SVM: + return "image/x-svm"; + case ConvertDataFormat::TIF: + return "image/tiff"; + case ConvertDataFormat::WMF: + return "image/x-wmf"; + case ConvertDataFormat::EMF: + return "image/x-emf"; + case ConvertDataFormat::SVG: + return "image/svg+xml"; + case ConvertDataFormat::MET: // What is this? + case ConvertDataFormat::Unknown: + default: + return ""; + } +} + +char const* GraphicMimeTypeHelper::GetExtensionForConvertDataFormat(ConvertDataFormat nFormat) +{ + char const* pExt = nullptr; + // create extension + if (nFormat != ConvertDataFormat::Unknown) + { + switch (nFormat) + { + case ConvertDataFormat::BMP: + pExt = ".bmp"; + break; + case ConvertDataFormat::GIF: + pExt = ".gif"; + break; + case ConvertDataFormat::JPG: + pExt = ".jpg"; + break; + case ConvertDataFormat::MET: + pExt = ".met"; + break; + case ConvertDataFormat::PCT: + pExt = ".pct"; + break; + case ConvertDataFormat::PNG: + pExt = ".png"; + break; + case ConvertDataFormat::SVM: + pExt = ".svm"; + break; + case ConvertDataFormat::TIF: + pExt = ".tif"; + break; + case ConvertDataFormat::WMF: + pExt = ".wmf"; + break; + case ConvertDataFormat::EMF: + pExt = ".emf"; + break; + + default: + pExt = ".grf"; + break; + } + } + return pExt; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/hash.cxx b/comphelper/source/misc/hash.cxx new file mode 100644 index 000000000..50fd8cd0f --- /dev/null +++ b/comphelper/source/misc/hash.cxx @@ -0,0 +1,257 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <com/sun/star/uno/RuntimeException.hpp> +#include <comphelper/hash.hxx> +#include <rtl/ustring.hxx> +#include <rtl/alloc.h> +#include <osl/endian.h> +#include <config_oox.h> + +#if USE_TLS_NSS +#include <nss.h> +#include <sechash.h> +#elif USE_TLS_OPENSSL +#include <openssl/evp.h> +#include <openssl/sha.h> +#endif // USE_TLS_OPENSSL + +namespace comphelper { + +struct HashImpl +{ + +#if USE_TLS_NSS + HASHContext* mpContext; + + HASH_HashType getNSSType() const + { + switch (meType) + { + case HashType::MD5: + return HASH_AlgMD5; + case HashType::SHA1: + return HASH_AlgSHA1; + case HashType::SHA256: + return HASH_AlgSHA256; + case HashType::SHA512: + return HASH_AlgSHA512; + } + + return HASH_AlgNULL; + } +#elif USE_TLS_OPENSSL + EVP_MD_CTX* mpContext; + + const EVP_MD* getOpenSSLType() const + { + switch (meType) + { + case HashType::MD5: + return EVP_md5(); + case HashType::SHA1: + return EVP_sha1(); + case HashType::SHA256: + return EVP_sha256(); + case HashType::SHA512: + return EVP_sha512(); + } + + return nullptr; + } +#endif + + HashType const meType; + + HashImpl(HashType eType): + meType(eType) + { + +#if USE_TLS_NSS + auto const e = NSS_NoDB_Init(nullptr); + if (e != SECSuccess) { + throw css::uno::RuntimeException("NSS_NoDB_Init failed with " + OUString::number(e)); + } + mpContext = HASH_Create(getNSSType()); + HASH_Begin(mpContext); +#elif USE_TLS_OPENSSL + mpContext = EVP_MD_CTX_create(); + EVP_DigestInit_ex(mpContext, getOpenSSLType(), nullptr); +#endif + } + + ~HashImpl() + { +#if USE_TLS_NSS + HASH_Destroy(mpContext); +#elif USE_TLS_OPENSSL + EVP_MD_CTX_destroy(mpContext); +#endif + } +}; + +Hash::Hash(HashType eType): + mpImpl(new HashImpl(eType)) +{ +} + +Hash::~Hash() +{ +} + +void Hash::update(const unsigned char* pInput, size_t length) +{ +#if USE_TLS_NSS + HASH_Update(mpImpl->mpContext, pInput, length); +#elif USE_TLS_OPENSSL + EVP_DigestUpdate(mpImpl->mpContext, pInput, length); +#else + (void)pInput; + (void)length; +#endif +} + +std::vector<unsigned char> Hash::finalize() +{ + std::vector<unsigned char> hash(getLength(), 0); + unsigned int digestWrittenLength; +#if USE_TLS_NSS + HASH_End(mpImpl->mpContext, hash.data(), &digestWrittenLength, getLength()); +#elif USE_TLS_OPENSSL + EVP_DigestFinal_ex(mpImpl->mpContext, hash.data(), &digestWrittenLength); +#else + (void)digestWrittenLength; +#endif + + return hash; +} + +size_t Hash::getLength() const +{ + switch (mpImpl->meType) + { + case HashType::MD5: + return MD5_HASH_LENGTH; + case HashType::SHA1: + return SHA1_HASH_LENGTH; + case HashType::SHA256: + return SHA256_HASH_LENGTH; + case HashType::SHA512: + return SHA512_HASH_LENGTH; + } + + return 0; +} + +std::vector<unsigned char> Hash::calculateHash(const unsigned char* pInput, size_t length, HashType eType) +{ + Hash aHash(eType); + aHash.update(pInput, length); + return aHash.finalize(); +} + +std::vector<unsigned char> Hash::calculateHash( + const unsigned char* pInput, size_t nLength, + const unsigned char* pSalt, size_t nSaltLen, + sal_uInt32 nSpinCount, + IterCount eIterCount, + HashType eType) +{ + if (!pSalt) + nSaltLen = 0; + + if (!nSaltLen && !nSpinCount) + return calculateHash( pInput, nLength, eType); + + Hash aHash(eType); + if (nSaltLen) + { + std::vector<unsigned char> initialData( nSaltLen + nLength); + std::copy( pSalt, pSalt + nSaltLen, initialData.begin()); + std::copy( pInput, pInput + nLength, initialData.begin() + nSaltLen); + aHash.update( initialData.data(), initialData.size()); + rtl_secureZeroMemory( initialData.data(), initialData.size()); + } + else + { + aHash.update( pInput, nLength); + } + std::vector<unsigned char> hash( aHash.finalize()); + + if (nSpinCount) + { + // https://msdn.microsoft.com/en-us/library/dd920692 + // says the iteration is concatenated after the hash. + // https://msdn.microsoft.com/en-us/library/dd924776 and + // https://msdn.microsoft.com/en-us/library/dd925430 + // say the iteration is prepended to the hash. + const size_t nAddIter = (eIterCount == IterCount::NONE ? 0 : 4); + const size_t nIterPos = (eIterCount == IterCount::APPEND ? hash.size() : 0); + const size_t nHashPos = (eIterCount == IterCount::PREPEND ? nAddIter : 0); + std::vector<unsigned char> data( hash.size() + nAddIter, 0); + for (sal_uInt32 i = 0; i < nSpinCount; ++i) + { + std::copy( hash.begin(), hash.end(), data.begin() + nHashPos); + if (nAddIter) + { +#ifdef OSL_BIGENDIAN + sal_uInt32 be = i; + sal_uInt8* p = reinterpret_cast<sal_uInt8*>(&be); + std::swap( p[0], p[3] ); + std::swap( p[1], p[2] ); + memcpy( data.data() + nIterPos, &be, nAddIter); +#else + memcpy( data.data() + nIterPos, &i, nAddIter); +#endif + } + /* TODO: isn't there something better than + * creating/finalizing/destroying on each iteration? */ + Hash aReHash(eType); + aReHash.update( data.data(), data.size()); + hash = aReHash.finalize(); + } + } + + return hash; +} + +std::vector<unsigned char> Hash::calculateHash( + const OUString& rPassword, + const std::vector<unsigned char>& rSaltValue, + sal_uInt32 nSpinCount, + IterCount eIterCount, + HashType eType) +{ + const unsigned char* pPassBytes = reinterpret_cast<const unsigned char*>(rPassword.getStr()); + const size_t nPassBytesLen = rPassword.getLength() * 2; +#ifdef OSL_BIGENDIAN + // Swap UTF16-BE to UTF16-LE + std::vector<unsigned char> vPass; + if (nPassBytesLen) + { + vPass.resize( nPassBytesLen); + std::copy( pPassBytes, pPassBytes + nPassBytesLen, vPass.begin()); + unsigned char* p = vPass.data(); + unsigned char const * const pEnd = p + nPassBytesLen; + for ( ; p < pEnd; p += 2 ) + { + std::swap( p[0], p[1] ); + } + pPassBytes = vPass.data(); + } +#endif + return calculateHash( pPassBytes, nPassBytesLen, rSaltValue.data(), rSaltValue.size(), nSpinCount, + eIterCount, eType); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/instancelocker.cxx b/comphelper/source/misc/instancelocker.cxx new file mode 100644 index 000000000..84c8054ec --- /dev/null +++ b/comphelper/source/misc/instancelocker.cxx @@ -0,0 +1,445 @@ +/* -*- 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 <cppuhelper/supportsservice.hxx> + +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/util/XCloseBroadcaster.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/frame/XDesktop.hpp> +#include <com/sun/star/frame/TerminationVetoException.hpp> +#include <com/sun/star/frame/DoubleInitializationException.hpp> +#include <com/sun/star/embed/Actions.hpp> +#include <com/sun/star/embed/XActionsApproval.hpp> +#include <utility> + +#include "instancelocker.hxx" + +namespace com::sun::star::uno { class XComponentContext; } + +using namespace ::com::sun::star; + + +// OInstanceLocker + + +OInstanceLocker::OInstanceLocker() +: m_bDisposed( false ) +, m_bInitialized( false ) +{ +} + + +OInstanceLocker::~OInstanceLocker() +{ + if ( !m_bDisposed ) + { + osl_atomic_increment(&m_refCount); // to call dispose + try { + dispose(); + } + catch ( uno::RuntimeException& ) + {} + } +} + +// XComponent + +void SAL_CALL OInstanceLocker::dispose() +{ + std::unique_lock aGuard( m_aMutex ); + + if ( m_bDisposed ) + throw lang::DisposedException(); + + lang::EventObject aSource( static_cast< ::cppu::OWeakObject* >(this) ); + m_aListenersContainer.disposeAndClear( aGuard, aSource ); + if ( m_xLockListener.is() ) + { + auto tmp = std::move(m_xLockListener); + aGuard.unlock(); + tmp->Dispose(); + aGuard.lock(); + } + + m_bDisposed = true; +} + + +void SAL_CALL OInstanceLocker::addEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + std::unique_lock aGuard( m_aMutex ); + if ( m_bDisposed ) + throw lang::DisposedException(); // TODO + + m_aListenersContainer.addInterface( aGuard, xListener ); +} + + +void SAL_CALL OInstanceLocker::removeEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + std::unique_lock aGuard( m_aMutex ); + m_aListenersContainer.removeInterface( aGuard, xListener ); +} + +// XInitialization + +void SAL_CALL OInstanceLocker::initialize( const uno::Sequence< uno::Any >& aArguments ) +{ + std::unique_lock aGuard( m_aMutex ); + if ( m_bInitialized ) + throw frame::DoubleInitializationException(); + + if ( m_bDisposed ) + throw lang::DisposedException(); // TODO + + if ( !m_refCount ) + throw uno::RuntimeException(); // the object must be refcounted already! + + uno::Reference< uno::XInterface > xInstance; + uno::Reference< embed::XActionsApproval > xApproval; + + try + { + sal_Int32 nLen = aArguments.getLength(); + if ( nLen < 2 || nLen > 3 ) + throw lang::IllegalArgumentException( + "Wrong count of parameters!", + uno::Reference< uno::XInterface >(), + 0 ); + + if ( !( aArguments[0] >>= xInstance ) || !xInstance.is() ) + throw lang::IllegalArgumentException( + "Nonempty reference is expected as the first argument!", + uno::Reference< uno::XInterface >(), + 0 ); + + sal_Int32 nModes = 0; + if ( + !( aArguments[1] >>= nModes ) || + ( + !( nModes & embed::Actions::PREVENT_CLOSE ) && + !( nModes & embed::Actions::PREVENT_TERMINATION ) + ) + ) + { + throw lang::IllegalArgumentException( + "The correct modes set is expected as the second argument!", + uno::Reference< uno::XInterface >(), + 0 ); + } + + if ( nLen == 3 && !( aArguments[2] >>= xApproval ) ) + throw lang::IllegalArgumentException( + "If the third argument is provided, it must be XActionsApproval implementation!", + uno::Reference< uno::XInterface >(), + 0 ); + + m_xLockListener = new OLockListener( uno::Reference< lang::XComponent > ( static_cast< lang::XComponent* >( this ) ), + xInstance, + nModes, + xApproval ); + m_xLockListener->Init(); + } + catch( uno::Exception& ) + { + aGuard.unlock(); + dispose(); + throw; + } + + m_bInitialized = true; +} + +// XServiceInfo +OUString SAL_CALL OInstanceLocker::getImplementationName( ) +{ + return "com.sun.star.comp.embed.InstanceLocker"; +} + +sal_Bool SAL_CALL OInstanceLocker::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence< OUString > SAL_CALL OInstanceLocker::getSupportedServiceNames() +{ + return { "com.sun.star.embed.InstanceLocker" }; +} + +// OLockListener + + +OLockListener::OLockListener( uno::WeakReference< lang::XComponent > xWrapper, + uno::Reference< uno::XInterface > xInstance, + sal_Int32 nMode, + uno::Reference< embed::XActionsApproval > xApproval ) +: m_xInstance(std::move( xInstance )) +, m_xApproval(std::move( xApproval )) +, m_xWrapper(std::move( xWrapper )) +, m_bDisposed( false ) +, m_bInitialized( false ) +, m_nMode( nMode ) +{ +} + + +OLockListener::~OLockListener() +{ +} + + +void OLockListener::Dispose() +{ + std::unique_lock aGuard( m_aMutex ); + + if ( m_bDisposed ) + return; + + auto xInstance = std::move(m_xInstance); + auto nMode = m_nMode; + m_bDisposed = true; + aGuard.unlock(); + + if ( nMode & embed::Actions::PREVENT_CLOSE ) + { + try + { + uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster( xInstance, uno::UNO_QUERY ); + if ( xCloseBroadcaster.is() ) + xCloseBroadcaster->removeCloseListener( static_cast< util::XCloseListener* >( this ) ); + + uno::Reference< util::XCloseable > xCloseable( xInstance, uno::UNO_QUERY ); + if ( xCloseable.is() ) + xCloseable->close( true ); + } + catch( uno::Exception& ) + {} + } + + if ( nMode & embed::Actions::PREVENT_TERMINATION ) + { + try + { + uno::Reference< frame::XDesktop > xDesktop( xInstance, uno::UNO_QUERY_THROW ); + xDesktop->removeTerminateListener( static_cast< frame::XTerminateListener* >( this ) ); + } + catch( uno::Exception& ) + {} + } +} + +// XEventListener + +void SAL_CALL OLockListener::disposing( const lang::EventObject& aEvent ) +{ + std::unique_lock aGuard( m_aMutex ); + + // object is disposed + if ( aEvent.Source != m_xInstance ) + return; + + // the object does not listen for anything any more + m_nMode = 0; + + // dispose the wrapper; + uno::Reference< lang::XComponent > xComponent( m_xWrapper.get(), uno::UNO_QUERY ); + aGuard.unlock(); + if ( xComponent.is() ) + { + try { xComponent->dispose(); } + catch( uno::Exception& ){} + } +} + + +// XCloseListener + +void SAL_CALL OLockListener::queryClosing( const lang::EventObject& aEvent, sal_Bool ) +{ + // GetsOwnership parameter is always ignored, the user of the service must close the object always + std::unique_lock aGuard( m_aMutex ); + if ( !(!m_bDisposed && aEvent.Source == m_xInstance && ( m_nMode & embed::Actions::PREVENT_CLOSE )) ) + return; + + try + { + uno::Reference< embed::XActionsApproval > xApprove = m_xApproval; + + // unlock the mutex here + aGuard.unlock(); + + if ( xApprove.is() && xApprove->approveAction( embed::Actions::PREVENT_CLOSE ) ) + throw util::CloseVetoException(); + } + catch( util::CloseVetoException& ) + { + // rethrow this exception + throw; + } + catch( uno::Exception& ) + { + // no action should be done + } +} + + +void SAL_CALL OLockListener::notifyClosing( const lang::EventObject& aEvent ) +{ + std::unique_lock aGuard( m_aMutex ); + + // object is closed, no reason to listen + if ( aEvent.Source != m_xInstance ) + return; + + uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster( aEvent.Source, uno::UNO_QUERY ); + if ( !xCloseBroadcaster.is() ) + return; + + xCloseBroadcaster->removeCloseListener( static_cast< util::XCloseListener* >( this ) ); + m_nMode &= ~embed::Actions::PREVENT_CLOSE; + if ( !m_nMode ) + { + // dispose the wrapper; + uno::Reference< lang::XComponent > xComponent( m_xWrapper.get(), uno::UNO_QUERY ); + aGuard.unlock(); + if ( xComponent.is() ) + { + try { xComponent->dispose(); } + catch( uno::Exception& ){} + } + } +} + + +// XTerminateListener + +void SAL_CALL OLockListener::queryTermination( const lang::EventObject& aEvent ) +{ + std::unique_lock aGuard( m_aMutex ); + if ( !(aEvent.Source == m_xInstance && ( m_nMode & embed::Actions::PREVENT_TERMINATION )) ) + return; + + try + { + uno::Reference< embed::XActionsApproval > xApprove = m_xApproval; + + // unlock the mutex here + aGuard.unlock(); + + if ( xApprove.is() && xApprove->approveAction( embed::Actions::PREVENT_TERMINATION ) ) + throw frame::TerminationVetoException(); + } + catch( frame::TerminationVetoException& ) + { + // rethrow this exception + throw; + } + catch( uno::Exception& ) + { + // no action should be done + } +} + + +void SAL_CALL OLockListener::notifyTermination( const lang::EventObject& aEvent ) +{ + std::unique_lock aGuard( m_aMutex ); + + // object is terminated, no reason to listen + if ( aEvent.Source != m_xInstance ) + return; + + uno::Reference< frame::XDesktop > xDesktop( aEvent.Source, uno::UNO_QUERY ); + if ( !xDesktop.is() ) + return; + + try + { + xDesktop->removeTerminateListener( static_cast< frame::XTerminateListener* >( this ) ); + m_nMode &= ~embed::Actions::PREVENT_TERMINATION; + if ( !m_nMode ) + { + // dispose the wrapper; + uno::Reference< lang::XComponent > xComponent( m_xWrapper.get(), uno::UNO_QUERY ); + aGuard.unlock(); + if ( xComponent.is() ) + { + try { xComponent->dispose(); } + catch( uno::Exception& ){} + } + } + } + catch( uno::Exception& ) + {} +} + + +// XInitialization + +void OLockListener::Init() +{ + std::unique_lock aGuard( m_aMutex ); + + if ( m_bDisposed || m_bInitialized ) + return; + + try + { + if ( m_nMode & embed::Actions::PREVENT_CLOSE ) + { + uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster( m_xInstance, uno::UNO_QUERY_THROW ); + xCloseBroadcaster->addCloseListener( static_cast< util::XCloseListener* >( this ) ); + } + + if ( m_nMode & embed::Actions::PREVENT_TERMINATION ) + { + uno::Reference< frame::XDesktop > xDesktop( m_xInstance, uno::UNO_QUERY_THROW ); + xDesktop->addTerminateListener( static_cast< frame::XTerminateListener* >( this ) ); + } + } + catch( uno::Exception& ) + { + // dispose the wrapper; + uno::Reference< lang::XComponent > xComponent( m_xWrapper.get(), uno::UNO_QUERY ); + aGuard.unlock(); + if ( xComponent.is() ) + { + try { xComponent->dispose(); } + catch( uno::Exception& ){} + } + + throw; + } + + m_bInitialized = true; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_embed_InstanceLocker( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new OInstanceLocker()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/instancelocker.hxx b/comphelper/source/misc/instancelocker.hxx new file mode 100644 index 000000000..6a050c7f4 --- /dev/null +++ b/comphelper/source/misc/instancelocker.hxx @@ -0,0 +1,111 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/util/XCloseListener.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <comphelper/interfacecontainer4.hxx> +#include <cppuhelper/weakref.hxx> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <mutex> + +namespace com::sun::star::embed { class XActionsApproval; } + + +class OLockListener; + +// the service is implemented as a wrapper to be able to die by refcount +// the disposing mechanics is required for java related scenarios +class OInstanceLocker : public ::cppu::WeakImplHelper< css::lang::XComponent, + css::lang::XInitialization, + css::lang::XServiceInfo > +{ + std::mutex m_aMutex; + + rtl::Reference< OLockListener > m_xLockListener; + + comphelper::OInterfaceContainerHelper4<css::lang::XEventListener> m_aListenersContainer; // list of listeners + + bool m_bDisposed; + bool m_bInitialized; + +public: + explicit OInstanceLocker(); + virtual ~OInstanceLocker() override; + +// XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + +// XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) 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; + +}; + + +class OLockListener : public ::cppu::WeakImplHelper< css::util::XCloseListener, + css::frame::XTerminateListener > +{ + std::mutex m_aMutex; + css::uno::Reference< css::uno::XInterface > m_xInstance; + css::uno::Reference< css::embed::XActionsApproval > m_xApproval; + + css::uno::WeakReference< css::lang::XComponent > m_xWrapper; + + bool m_bDisposed; + bool m_bInitialized; + + sal_Int32 m_nMode; + +public: + OLockListener( css::uno::WeakReference< css::lang::XComponent > xWrapper, + css::uno::Reference< css::uno::XInterface > xInstance, + sal_Int32 nMode, + css::uno::Reference< css::embed::XActionsApproval > xApproval ); + + virtual ~OLockListener() override; + + void Init(); + void Dispose(); + +// XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + +// XCloseListener + virtual void SAL_CALL queryClosing( const css::lang::EventObject& Source, sal_Bool GetsOwnership ) override; + virtual void SAL_CALL notifyClosing( const css::lang::EventObject& Source ) override; + +// XTerminateListener + virtual void SAL_CALL queryTermination( const css::lang::EventObject& Event ) override; + virtual void SAL_CALL notifyTermination( const css::lang::EventObject& Event ) override; + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/interaction.cxx b/comphelper/source/misc/interaction.cxx new file mode 100644 index 000000000..9e7b1706d --- /dev/null +++ b/comphelper/source/misc/interaction.cxx @@ -0,0 +1,71 @@ +/* -*- 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/interaction.hxx> + +#include <comphelper/sequence.hxx> +#include <utility> +#include <osl/diagnose.h> + + +namespace comphelper +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::task; + + OInteractionRequest::OInteractionRequest(Any _aRequestDescription) + :m_aRequest(std::move(_aRequestDescription)) + { + } + + OInteractionRequest::OInteractionRequest(Any aRequestDescription, + std::vector<Reference<XInteractionContinuation>>&& rContinuations) + : m_aRequest(std::move(aRequestDescription)) + , m_aContinuations(std::move(rContinuations)) + { + } + + void OInteractionRequest::addContinuation(const Reference< XInteractionContinuation >& _rxContinuation) + { + OSL_ENSURE(_rxContinuation.is(), "OInteractionRequest::addContinuation: invalid argument!"); + if (_rxContinuation.is()) + { + m_aContinuations.push_back(_rxContinuation); + } + } + + + Any SAL_CALL OInteractionRequest::getRequest( ) + { + return m_aRequest; + } + + + Sequence< Reference< XInteractionContinuation > > SAL_CALL OInteractionRequest::getContinuations( ) + { + return comphelper::containerToSequence(m_aContinuations); + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/logging.cxx b/comphelper/source/misc/logging.cxx new file mode 100644 index 000000000..b656389de --- /dev/null +++ b/comphelper/source/misc/logging.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/logging.hxx> + +#include <com/sun/star/logging/LoggerPool.hpp> + +#include <osl/diagnose.h> + + +namespace comphelper +{ + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::logging::XLoggerPool; + using ::com::sun::star::logging::LoggerPool; + using ::com::sun::star::logging::XLogger; + using ::com::sun::star::uno::Exception; + + class EventLogger_Impl + { + private: + Reference< XComponentContext > m_aContext; + Reference< XLogger > m_xLogger; + + public: + EventLogger_Impl( const Reference< XComponentContext >& _rxContext, const OUString& _rLoggerName ); + + bool isValid() const { return m_xLogger.is(); } + const Reference< XLogger >& getLogger() const { return m_xLogger; } + }; + + EventLogger_Impl::EventLogger_Impl( const Reference< XComponentContext >& _rxContext, const OUString& _rLoggerName ) + :m_aContext( _rxContext ) + { + try + { + Reference< XLoggerPool > xPool( LoggerPool::get( m_aContext ) ); + if ( !_rLoggerName.isEmpty() ) + m_xLogger = xPool->getNamedLogger( _rLoggerName ); + else + m_xLogger = xPool->getDefaultLogger(); + } + catch( const Exception& ) + { + OSL_FAIL( "EventLogger_Impl::impl_createLogger_nothrow: caught an exception!" ); + } + } + + EventLogger::EventLogger( const Reference< XComponentContext >& _rxContext, const char* _pAsciiLoggerName ) + :m_pImpl( std::make_shared<EventLogger_Impl>( _rxContext, OUString::createFromAscii( _pAsciiLoggerName ) ) ) + { + } + + bool EventLogger::isLoggable( const sal_Int32 _nLogLevel ) const + { + if ( !m_pImpl->isValid() ) + return false; + + try + { + return m_pImpl->getLogger()->isLoggable( _nLogLevel ); + } + catch( const Exception& ) + { + OSL_FAIL( "EventLogger::isLoggable: caught an exception!" ); + } + + return false; + } + + const css::uno::Reference<css::logging::XLogger> & EventLogger::getLogger() const + { + return m_pImpl->getLogger(); + } + + + namespace + { + void lcl_replaceParameter( OUString& _inout_Message, const char* _rPlaceHolder, std::u16string_view _rReplacement ) + { + sal_Int32 nPlaceholderPosition = _inout_Message.indexOfAsciiL( _rPlaceHolder, strlen(_rPlaceHolder) ); + OSL_ENSURE( nPlaceholderPosition >= 0, "lcl_replaceParameter: placeholder not found!" ); + if ( nPlaceholderPosition < 0 ) + return; + + _inout_Message = _inout_Message.replaceAt( nPlaceholderPosition, strlen(_rPlaceHolder), _rReplacement ); + } + } + + + void EventLogger::impl_log( const sal_Int32 _nLogLevel, + const char* _pSourceClass, const char* _pSourceMethod, const OUString& _rMessage, + const OptionalString& _rArgument1, const OptionalString& _rArgument2, + const OptionalString& _rArgument3, const OptionalString& _rArgument4, + const OptionalString& _rArgument5, const OptionalString& _rArgument6 ) const + { + OUString sMessage( _rMessage ); + if ( !!_rArgument1 ) + lcl_replaceParameter( sMessage, "$1$", *_rArgument1 ); + + if ( !!_rArgument2 ) + lcl_replaceParameter( sMessage, "$2$", *_rArgument2 ); + + if ( !!_rArgument3 ) + lcl_replaceParameter( sMessage, "$3$", *_rArgument3 ); + + if ( !!_rArgument4 ) + lcl_replaceParameter( sMessage, "$4$", *_rArgument4 ); + + if ( !!_rArgument5 ) + lcl_replaceParameter( sMessage, "$5$", *_rArgument5 ); + + if ( !!_rArgument6 ) + lcl_replaceParameter( sMessage, "$6$", *_rArgument6 ); + + try + { + Reference< XLogger > xLogger( m_pImpl->getLogger() ); + OSL_PRECOND( xLogger.is(), "EventLogger::impl_log: should never be called without a logger!" ); + if ( _pSourceClass && _pSourceMethod ) + { + xLogger->logp( + _nLogLevel, + OUString::createFromAscii( _pSourceClass ), + OUString::createFromAscii( _pSourceMethod ), + sMessage + ); + } + else + { + xLogger->log( _nLogLevel, sMessage ); + } + } + catch( const Exception& ) + { + OSL_FAIL( "EventLogger::impl_log: caught an exception!" ); + } + } +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/lok.cxx b/comphelper/source/misc/lok.cxx new file mode 100644 index 000000000..b11bf4e83 --- /dev/null +++ b/comphelper/source/misc/lok.cxx @@ -0,0 +1,287 @@ +/* -*- 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/. + */ + +#include <comphelper/lok.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <sal/log.hxx> + +#include <iostream> + +namespace comphelper::LibreOfficeKit +{ + +static bool g_bActive(false); + +static bool g_bPartInInvalidation(false); + +static bool g_bTiledPainting(false); + +static bool g_bDialogPainting(false); + +static bool g_bTiledAnnotations(true); + +static bool g_bRangeHeaders(false); + +static bool g_bViewIdForVisCursorInvalidation(false); + +static bool g_bLocalRendering(false); + +static Compat g_eCompatFlags(Compat::none); + +namespace +{ + +class LanguageAndLocale +{ +private: + LanguageTag maLanguageTag; + LanguageTag maLocaleLanguageTag; + +public: + + LanguageAndLocale() + : maLanguageTag(LANGUAGE_NONE) + , maLocaleLanguageTag(LANGUAGE_NONE) + {} + + const LanguageTag& getLanguage() const + { + return maLanguageTag; + } + + void setLanguage(const LanguageTag& rLanguageTag) + { + if (maLanguageTag != rLanguageTag) + { + SAL_INFO("comphelper.lok", "Setting language from " << maLanguageTag.getBcp47() << " to " << rLanguageTag.getBcp47()); + maLanguageTag = rLanguageTag; + } + } + + const LanguageTag& getLocale() const + { + return maLocaleLanguageTag; + } + + void setLocale(const LanguageTag& rLocaleLanguageTag) + { + if (maLocaleLanguageTag != rLocaleLanguageTag) + { + SAL_INFO("comphelper.lok", "Setting locale from " << maLocaleLanguageTag.getBcp47() << " to " << rLocaleLanguageTag.getBcp47()); + maLocaleLanguageTag = rLocaleLanguageTag; + } + } + +}; + +} + +static LanguageAndLocale g_aLanguageAndLocale; + +/// Scaling of the cairo canvas painting for hi-dpi +static double g_fDPIScale(1.0); + +void setActive(bool bActive) +{ + g_bActive = bActive; +} + +bool isActive() +{ + return g_bActive; +} + +void setPartInInvalidation(bool bPartInInvalidation) +{ + g_bPartInInvalidation = bPartInInvalidation; +} + +bool isPartInInvalidation() +{ + return g_bPartInInvalidation; +} + +void setTiledPainting(bool bTiledPainting) +{ + g_bTiledPainting = bTiledPainting; +} + +bool isTiledPainting() +{ + return g_bTiledPainting; +} + +void setDialogPainting(bool bDialogPainting) +{ + g_bDialogPainting = bDialogPainting; +} + +bool isDialogPainting() +{ + return g_bDialogPainting; +} + +void setDPIScale(double fDPIScale) +{ + g_fDPIScale = fDPIScale; +} + +double getDPIScale() +{ + return g_fDPIScale; +} + +void setTiledAnnotations(bool bTiledAnnotations) +{ + g_bTiledAnnotations = bTiledAnnotations; +} + +bool isTiledAnnotations() +{ + return g_bTiledAnnotations; +} + +void setRangeHeaders(bool bRangeHeaders) +{ + g_bRangeHeaders = bRangeHeaders; +} + +void setViewIdForVisCursorInvalidation(bool bViewIdForVisCursorInvalidation) +{ + g_bViewIdForVisCursorInvalidation = bViewIdForVisCursorInvalidation; +} + +bool isViewIdForVisCursorInvalidation() +{ + return g_bViewIdForVisCursorInvalidation; +} + +bool isRangeHeaders() +{ + return g_bRangeHeaders; +} + +void setLocalRendering(bool bLocalRendering) +{ + g_bLocalRendering = bLocalRendering; +} + +bool isLocalRendering() +{ + return g_bLocalRendering; +} + +void setCompatFlag(Compat flag) { g_eCompatFlags = static_cast<Compat>(g_eCompatFlags | flag); } + +bool isCompatFlagSet(Compat flag) { return (g_eCompatFlags & flag) == flag; } + +void setLocale(const LanguageTag& rLanguageTag) +{ + g_aLanguageAndLocale.setLocale(rLanguageTag); +} + +const LanguageTag& getLocale() +{ + const LanguageTag& rLocale = g_aLanguageAndLocale.getLocale(); + SAL_INFO_IF(rLocale.getLanguageType() == LANGUAGE_NONE, "comphelper.lok", "Locale not set"); + return rLocale; +} + +void setLanguageTag(const LanguageTag& rLanguageTag) +{ + g_aLanguageAndLocale.setLanguage(rLanguageTag); +} + +const LanguageTag& getLanguageTag() +{ + const LanguageTag& rLanguage = g_aLanguageAndLocale.getLanguage(); + SAL_INFO_IF(rLanguage.getLanguageType() == LANGUAGE_NONE, "comphelper.lok", "Language not set"); + return rLanguage; +} + +bool isAllowlistedLanguage(const OUString& lang) +{ + if (!isActive()) + return true; + +#if defined ANDROID || defined IOS + (void) lang; + return true; +#else + static const std::vector<OUString> aAllowlist = [] { + std::vector<OUString> aList; + // coverity[tainted_data] - we trust the contents of this variable + const char* pAllowlist = getenv("LOK_ALLOWLIST_LANGUAGES"); + if (pAllowlist) + { + std::stringstream stream(pAllowlist); + std::string s; + + std::cerr << "Allowlisted languages: "; + while (getline(stream, s, ' ')) { + if (s.length() == 0) + continue; + + std::cerr << s << " "; + aList.emplace_back(OStringToOUString(s.c_str(), RTL_TEXTENCODING_UTF8)); + } + std::cerr << std::endl; + } + + if (aList.empty()) + std::cerr << "No language allowlisted, turning off the language support." << std::endl; + + return aList; + }(); + + if (aAllowlist.empty()) + return false; + + for (const auto& entry : aAllowlist) + { + if (lang.startsWith(entry)) + return true; + if (lang.startsWith(entry.replace('_', '-'))) + return true; + } + + return false; +#endif +} + +static void (*pStatusIndicatorCallback)(void *data, statusIndicatorCallbackType type, int percent, const char* pText)(nullptr); +static void *pStatusIndicatorCallbackData(nullptr); + +void setStatusIndicatorCallback(void (*callback)(void *data, statusIndicatorCallbackType type, int percent, const char* pText), void *data) +{ + pStatusIndicatorCallback = callback; + pStatusIndicatorCallbackData = data; +} + +void statusIndicatorStart(const OUString& sText) +{ + if (pStatusIndicatorCallback) + pStatusIndicatorCallback(pStatusIndicatorCallbackData, statusIndicatorCallbackType::Start, 0, sText.toUtf8().getStr()); +} + +void statusIndicatorSetValue(int percent) +{ + if (pStatusIndicatorCallback) + pStatusIndicatorCallback(pStatusIndicatorCallbackData, statusIndicatorCallbackType::SetValue, percent, nullptr); +} + +void statusIndicatorFinish() +{ + if (pStatusIndicatorCallback) + pStatusIndicatorCallback(pStatusIndicatorCallbackData, statusIndicatorCallbackType::Finish, 0, nullptr); +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/mimeconfighelper.cxx b/comphelper/source/misc/mimeconfighelper.cxx new file mode 100644 index 000000000..7f402b635 --- /dev/null +++ b/comphelper/source/misc/mimeconfighelper.cxx @@ -0,0 +1,909 @@ +/* -*- 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/beans/PropertyValue.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XContainerQuery.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/embed/VerbDescriptor.hpp> +#include <com/sun/star/document/XTypeDetection.hpp> + +#include <osl/diagnose.h> + +#include <comphelper/fileformat.h> +#include <comphelper/mimeconfighelper.hxx> +#include <comphelper/classids.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/documentconstants.hxx> +#include <comphelper/propertysequence.hxx> +#include <rtl/ustrbuf.hxx> +#include <utility> + + +using namespace ::com::sun::star; +using namespace comphelper; + + +MimeConfigurationHelper::MimeConfigurationHelper( uno::Reference< uno::XComponentContext > xContext ) +: m_xContext(std::move( xContext )) +{ + if ( !m_xContext.is() ) + throw uno::RuntimeException(); +} + + +OUString MimeConfigurationHelper::GetStringClassIDRepresentation( const uno::Sequence< sal_Int8 >& aClassID ) +{ + OUStringBuffer aResult; + + if ( aClassID.getLength() == 16 ) + { + for ( sal_Int32 nInd = 0; nInd < aClassID.getLength(); nInd++ ) + { + if ( nInd == 4 || nInd == 6 || nInd == 8 || nInd == 10 ) + aResult.append("-"); + + sal_Int32 nDigit1 = static_cast<sal_Int32>( static_cast<sal_uInt8>(aClassID[nInd]) / 16 ); + sal_Int32 nDigit2 = static_cast<sal_uInt8>(aClassID[nInd]) % 16; + aResult.append( OUString::number(nDigit1, 16) + OUString::number( nDigit2, 16 ) ); + } + } + + return aResult.makeStringAndClear(); +} + + +static sal_uInt8 GetDigit_Impl( char aChar ) +{ + if ( aChar >= '0' && aChar <= '9' ) + return aChar - '0'; + else if ( aChar >= 'a' && aChar <= 'f' ) + return aChar - 'a' + 10; + else if ( aChar >= 'A' && aChar <= 'F' ) + return aChar - 'A' + 10; + else + return 16; +} + + +uno::Sequence< sal_Int8 > MimeConfigurationHelper::GetSequenceClassIDRepresentation( std::u16string_view aClassID ) +{ + size_t nLength = aClassID.size(); + if ( nLength == 36 ) + { + OString aCharClassID = OUStringToOString( aClassID, RTL_TEXTENCODING_ASCII_US ); + uno::Sequence< sal_Int8 > aResult( 16 ); + auto pResult = aResult.getArray(); + + size_t nStrPointer = 0; + sal_Int32 nSeqInd = 0; + while( nSeqInd < 16 && nStrPointer + 1U < nLength ) + { + sal_uInt8 nDigit1 = GetDigit_Impl( aCharClassID[nStrPointer++] ); + sal_uInt8 nDigit2 = GetDigit_Impl( aCharClassID[nStrPointer++] ); + + if ( nDigit1 > 15 || nDigit2 > 15 ) + break; + + pResult[nSeqInd++] = static_cast<sal_Int8>( nDigit1 * 16 + nDigit2 ); + + if ( nStrPointer < nLength && aCharClassID[nStrPointer] == '-' ) + nStrPointer++; + } + + if ( nSeqInd == 16 && nStrPointer == nLength ) + return aResult; + } + + return uno::Sequence< sal_Int8 >(); +} + + +uno::Reference< container::XNameAccess > MimeConfigurationHelper::GetConfigurationByPathImpl( const OUString& aPath ) +{ + uno::Reference< container::XNameAccess > xConfig; + + try + { + if ( !m_xConfigProvider.is() ) + m_xConfigProvider = configuration::theDefaultProvider::get( m_xContext ); + + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(aPath)} + })); + xConfig.set( m_xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", + aArgs ), + uno::UNO_QUERY ); + } + catch( uno::Exception& ) + {} + + return xConfig; +} + + +uno::Reference< container::XNameAccess > MimeConfigurationHelper::GetObjConfiguration() +{ + std::unique_lock aGuard( m_aMutex ); + + if ( !m_xObjectConfig.is() ) + m_xObjectConfig = GetConfigurationByPathImpl( + "/org.openoffice.Office.Embedding/Objects" ); + + return m_xObjectConfig; +} + + +uno::Reference< container::XNameAccess > MimeConfigurationHelper::GetVerbsConfiguration() +{ + std::unique_lock aGuard( m_aMutex ); + + if ( !m_xVerbsConfig.is() ) + m_xVerbsConfig = GetConfigurationByPathImpl( + "/org.openoffice.Office.Embedding/Verbs"); + + return m_xVerbsConfig; +} + + +uno::Reference< container::XNameAccess > MimeConfigurationHelper::GetMediaTypeConfiguration() +{ + std::unique_lock aGuard( m_aMutex ); + + if ( !m_xMediaTypeConfig.is() ) + m_xMediaTypeConfig = GetConfigurationByPathImpl( + "/org.openoffice.Office.Embedding/MimeTypeClassIDRelations"); + + return m_xMediaTypeConfig; +} + + +uno::Reference< container::XNameAccess > MimeConfigurationHelper::GetFilterFactory() +{ + std::unique_lock aGuard( m_aMutex ); + + if ( !m_xFilterFactory.is() ) + m_xFilterFactory.set( + m_xContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.FilterFactory", m_xContext), + uno::UNO_QUERY ); + + return m_xFilterFactory; +} + + +OUString MimeConfigurationHelper::GetDocServiceNameFromFilter( const OUString& aFilterName ) +{ + OUString aDocServiceName; + + try + { + uno::Reference< container::XNameAccess > xFilterFactory( + GetFilterFactory(), + uno::UNO_SET_THROW ); + + uno::Any aFilterAnyData = xFilterFactory->getByName( aFilterName ); + uno::Sequence< beans::PropertyValue > aFilterData; + if ( aFilterAnyData >>= aFilterData ) + { + for ( const auto & prop : std::as_const(aFilterData) ) + if ( prop.Name == "DocumentService" ) + prop.Value >>= aDocServiceName; + } + } + catch( uno::Exception& ) + {} + + return aDocServiceName; +} + + +OUString MimeConfigurationHelper::GetDocServiceNameFromMediaType( const OUString& aMediaType ) +{ + uno::Reference< container::XContainerQuery > xTypeCFG( + m_xContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.TypeDetection", m_xContext), + uno::UNO_QUERY ); + + if ( xTypeCFG.is() ) + { + try + { + // make query for all types matching the properties + uno::Sequence < beans::NamedValue > aSeq { { "MediaType", css::uno::Any(aMediaType) } }; + + uno::Reference < container::XEnumeration > xEnum = xTypeCFG->createSubSetEnumerationByProperties( aSeq ); + while ( xEnum->hasMoreElements() ) + { + uno::Sequence< beans::PropertyValue > aType; + if ( xEnum->nextElement() >>= aType ) + { + for ( const auto & prop : std::as_const(aType) ) + { + OUString aFilterName; + if ( prop.Name == "PreferredFilter" + && ( prop.Value >>= aFilterName ) && !aFilterName.isEmpty() ) + { + OUString aDocumentName = GetDocServiceNameFromFilter( aFilterName ); + if ( !aDocumentName.isEmpty() ) + return aDocumentName; + } + } + } + } + } + catch( uno::Exception& ) + {} + } + + return OUString(); +} + + +bool MimeConfigurationHelper::GetVerbByShortcut( const OUString& aVerbShortcut, + embed::VerbDescriptor& aDescriptor ) +{ + bool bResult = false; + + uno::Reference< container::XNameAccess > xVerbsConfig = GetVerbsConfiguration(); + uno::Reference< container::XNameAccess > xVerbsProps; + try + { + if ( xVerbsConfig.is() && ( xVerbsConfig->getByName( aVerbShortcut ) >>= xVerbsProps ) && xVerbsProps.is() ) + { + embed::VerbDescriptor aTempDescr; + static constexpr OUStringLiteral sVerbID = u"VerbID"; + static constexpr OUStringLiteral sVerbUIName = u"VerbUIName"; + static constexpr OUStringLiteral sVerbFlags = u"VerbFlags"; + static constexpr OUStringLiteral sVerbAttributes = u"VerbAttributes"; + if ( ( xVerbsProps->getByName(sVerbID) >>= aTempDescr.VerbID ) + && ( xVerbsProps->getByName(sVerbUIName) >>= aTempDescr.VerbName ) + && ( xVerbsProps->getByName(sVerbFlags) >>= aTempDescr.VerbFlags ) + && ( xVerbsProps->getByName(sVerbAttributes) >>= aTempDescr.VerbAttributes ) ) + { + aDescriptor = aTempDescr; + bResult = true; + } + } + } + catch( uno::Exception& ) + { + } + + return bResult; +} + + +uno::Sequence< beans::NamedValue > MimeConfigurationHelper::GetObjPropsFromConfigEntry( + const uno::Sequence< sal_Int8 >& aClassID, + const uno::Reference< container::XNameAccess >& xObjectProps ) +{ + uno::Sequence< beans::NamedValue > aResult; + + if ( aClassID.getLength() == 16 ) + { + try + { + const uno::Sequence< OUString > aObjPropNames = xObjectProps->getElementNames(); + + aResult.realloc( aObjPropNames.getLength() + 1 ); + auto pResult = aResult.getArray(); + pResult[0].Name = "ClassID"; + pResult[0].Value <<= aClassID; + + for ( sal_Int32 nInd = 0; nInd < aObjPropNames.getLength(); nInd++ ) + { + pResult[nInd + 1].Name = aObjPropNames[nInd]; + + if ( aObjPropNames[nInd] == "ObjectVerbs" ) + { + uno::Sequence< OUString > aVerbShortcuts; + if ( !(xObjectProps->getByName( aObjPropNames[nInd] ) >>= aVerbShortcuts) ) + throw uno::RuntimeException(); + uno::Sequence< embed::VerbDescriptor > aVerbDescriptors( aVerbShortcuts.getLength() ); + auto aVerbDescriptorsRange = asNonConstRange(aVerbDescriptors); + for ( sal_Int32 nVerbI = 0; nVerbI < aVerbShortcuts.getLength(); nVerbI++ ) + if ( !GetVerbByShortcut( aVerbShortcuts[nVerbI], aVerbDescriptorsRange[nVerbI] ) ) + throw uno::RuntimeException(); + + pResult[nInd+1].Value <<= aVerbDescriptors; + } + else + pResult[nInd+1].Value = xObjectProps->getByName( aObjPropNames[nInd] ); + } + } + catch( uno::Exception& ) + { + aResult.realloc( 0 ); + } + } + + return aResult; +} + + +OUString MimeConfigurationHelper::GetExplicitlyRegisteredObjClassID( const OUString& aMediaType ) +{ + OUString aStringClassID; + + uno::Reference< container::XNameAccess > xMediaTypeConfig = GetMediaTypeConfiguration(); + try + { + if ( xMediaTypeConfig.is() ) + xMediaTypeConfig->getByName( aMediaType ) >>= aStringClassID; + } + catch( uno::Exception& ) + { + } + + return aStringClassID; + +} + + +uno::Sequence< beans::NamedValue > MimeConfigurationHelper::GetObjectPropsByStringClassID( + const OUString& aStringClassID ) +{ + uno::Sequence< beans::NamedValue > aObjProps; + + uno::Sequence< sal_Int8 > aClassID = GetSequenceClassIDRepresentation( aStringClassID ); + if ( ClassIDsEqual( aClassID, GetSequenceClassID( SO3_DUMMY_CLASSID ) ) ) + { + aObjProps = { { "ObjectFactory", + uno::Any(OUString("com.sun.star.embed.OOoSpecialEmbeddedObjectFactory")) }, + { "ClassID", uno::Any(aClassID) } }; + return aObjProps; + } + + if ( aClassID.getLength() == 16 ) + { + uno::Reference< container::XNameAccess > xObjConfig = GetObjConfiguration(); + uno::Reference< container::XNameAccess > xObjectProps; + try + { + // TODO/LATER: allow to provide ClassID string in any format, only digits are counted + if ( xObjConfig.is() && ( xObjConfig->getByName( aStringClassID.toAsciiUpperCase() ) >>= xObjectProps ) && xObjectProps.is() ) + aObjProps = GetObjPropsFromConfigEntry( aClassID, xObjectProps ); + } + catch( uno::Exception& ) + { + } + } + + return aObjProps; +} + + +uno::Sequence< beans::NamedValue > MimeConfigurationHelper::GetObjectPropsByClassID( + const uno::Sequence< sal_Int8 >& aClassID ) +{ + uno::Sequence< beans::NamedValue > aObjProps; + if ( ClassIDsEqual( aClassID, GetSequenceClassID( SO3_DUMMY_CLASSID ) ) ) + { + aObjProps = { { "ObjectFactory", + uno::Any(OUString("com.sun.star.embed.OOoSpecialEmbeddedObjectFactory")) }, + { "ClassID", uno::Any(aClassID) } }; + } + + OUString aStringClassID = GetStringClassIDRepresentation( aClassID ); + if ( !aStringClassID.isEmpty() ) + { + uno::Reference< container::XNameAccess > xObjConfig = GetObjConfiguration(); + uno::Reference< container::XNameAccess > xObjectProps; + try + { + if ( xObjConfig.is() && ( xObjConfig->getByName( aStringClassID.toAsciiUpperCase() ) >>= xObjectProps ) && xObjectProps.is() ) + aObjProps = GetObjPropsFromConfigEntry( aClassID, xObjectProps ); + } + catch( uno::Exception& ) + { + } + } + + return aObjProps; +} + + +uno::Sequence< beans::NamedValue > MimeConfigurationHelper::GetObjectPropsByMediaType( const OUString& aMediaType ) +{ + uno::Sequence< beans::NamedValue > aObject = + GetObjectPropsByStringClassID( GetExplicitlyRegisteredObjClassID( aMediaType ) ); + if ( aObject.hasElements() ) + return aObject; + + OUString aDocumentName = GetDocServiceNameFromMediaType( aMediaType ); + if ( !aDocumentName.isEmpty() ) + return GetObjectPropsByDocumentName( aDocumentName ); + + return uno::Sequence< beans::NamedValue >(); +} + + +uno::Sequence< beans::NamedValue > MimeConfigurationHelper::GetObjectPropsByFilter( const OUString& aFilterName ) +{ + OUString aDocumentName = GetDocServiceNameFromFilter( aFilterName ); + if ( !aDocumentName.isEmpty() ) + return GetObjectPropsByDocumentName( aDocumentName ); + + return uno::Sequence< beans::NamedValue >(); +} + + +uno::Sequence< beans::NamedValue > MimeConfigurationHelper::GetObjectPropsByDocumentName( std::u16string_view aDocName ) +{ + if ( !aDocName.empty() ) + { + uno::Reference< container::XNameAccess > xObjConfig = GetObjConfiguration(); + if ( xObjConfig.is() ) + { + try + { + const uno::Sequence< OUString > aClassIDs = xObjConfig->getElementNames(); + for ( const OUString & id : aClassIDs ) + { + uno::Reference< container::XNameAccess > xObjectProps; + OUString aEntryDocName; + + if ( ( xObjConfig->getByName( id ) >>= xObjectProps ) && xObjectProps.is() + && ( xObjectProps->getByName("ObjectDocumentServiceName") >>= aEntryDocName ) + && aEntryDocName == aDocName ) + { + return GetObjPropsFromConfigEntry( GetSequenceClassIDRepresentation( id ), + xObjectProps ); + } + } + } + catch( uno::Exception& ) + {} + } + } + + return uno::Sequence< beans::NamedValue >(); +} + + +OUString MimeConfigurationHelper::GetFactoryNameByClassID( const uno::Sequence< sal_Int8 >& aClassID ) +{ + return GetFactoryNameByStringClassID( GetStringClassIDRepresentation( aClassID ) ); +} + + +OUString MimeConfigurationHelper::GetFactoryNameByStringClassID( const OUString& aStringClassID ) +{ + OUString aResult; + + if ( !aStringClassID.isEmpty() ) + { + uno::Reference< container::XNameAccess > xObjConfig = GetObjConfiguration(); + uno::Reference< container::XNameAccess > xObjectProps; + try + { + if ( xObjConfig.is() && ( xObjConfig->getByName( aStringClassID.toAsciiUpperCase() ) >>= xObjectProps ) && xObjectProps.is() ) + xObjectProps->getByName("ObjectFactory") >>= aResult; + } + catch( uno::Exception& ) + { + uno::Sequence< sal_Int8 > aClassID = GetSequenceClassIDRepresentation( aStringClassID ); + if ( ClassIDsEqual( aClassID, GetSequenceClassID( SO3_DUMMY_CLASSID ) ) ) + return "com.sun.star.embed.OOoSpecialEmbeddedObjectFactory"; + } + } + + return aResult; +} + + +OUString MimeConfigurationHelper::GetFactoryNameByDocumentName( std::u16string_view aDocName ) +{ + OUString aResult; + + if ( !aDocName.empty() ) + { + uno::Reference< container::XNameAccess > xObjConfig = GetObjConfiguration(); + if ( xObjConfig.is() ) + { + try + { + const uno::Sequence< OUString > aClassIDs = xObjConfig->getElementNames(); + for ( const OUString & id : aClassIDs ) + { + uno::Reference< container::XNameAccess > xObjectProps; + OUString aEntryDocName; + + if ( ( xObjConfig->getByName( id ) >>= xObjectProps ) && xObjectProps.is() + && ( xObjectProps->getByName( "ObjectDocumentServiceName" ) >>= aEntryDocName ) + && aEntryDocName == aDocName ) + { + xObjectProps->getByName("ObjectFactory") >>= aResult; + break; + } + } + } + catch( uno::Exception& ) + {} + } + } + + return aResult; +} + + +OUString MimeConfigurationHelper::GetFactoryNameByMediaType( const OUString& aMediaType ) +{ + OUString aResult = GetFactoryNameByStringClassID( GetExplicitlyRegisteredObjClassID( aMediaType ) ); + + if ( aResult.isEmpty() ) + { + OUString aDocumentName = GetDocServiceNameFromMediaType( aMediaType ); + if ( !aDocumentName.isEmpty() ) + aResult = GetFactoryNameByDocumentName( aDocumentName ); + } + + return aResult; +} + + +OUString MimeConfigurationHelper::UpdateMediaDescriptorWithFilterName( + uno::Sequence< beans::PropertyValue >& aMediaDescr, + bool bIgnoreType ) +{ + OUString aFilterName; + + for ( const auto & prop : std::as_const(aMediaDescr) ) + if ( prop.Name == "FilterName" ) + prop.Value >>= aFilterName; + + if ( aFilterName.isEmpty() ) + { + // filter name is not specified, so type detection should be done + + uno::Reference< document::XTypeDetection > xTypeDetection( + m_xContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.TypeDetection", m_xContext), + uno::UNO_QUERY_THROW ); + + // typedetection can change the mode, add a stream and so on, thus a copy should be used + uno::Sequence< beans::PropertyValue > aTempMD( aMediaDescr ); + + // get TypeName + OUString aTypeName = xTypeDetection->queryTypeByDescriptor( aTempMD, true ); + + // get FilterName + for ( const auto & prop : std::as_const(aTempMD) ) + if ( prop.Name == "FilterName" ) + prop.Value >>= aFilterName; + + if ( !aFilterName.isEmpty() ) + { + sal_Int32 nOldLen = aMediaDescr.getLength(); + aMediaDescr.realloc( nOldLen + 1 ); + auto pMediaDescr = aMediaDescr.getArray(); + pMediaDescr[nOldLen].Name = "FilterName"; + pMediaDescr[ nOldLen ].Value <<= aFilterName; + + } + else if ( !aTypeName.isEmpty() && !bIgnoreType ) + { + uno::Reference< container::XNameAccess > xNameAccess( xTypeDetection, uno::UNO_QUERY ); + uno::Sequence< beans::PropertyValue > aTypes; + + if ( xNameAccess.is() && ( xNameAccess->getByName( aTypeName ) >>= aTypes ) ) + { + for ( const auto & prop : std::as_const(aTypes) ) + { + if ( prop.Name == "PreferredFilter" && ( prop.Value >>= aFilterName ) ) + { + sal_Int32 nOldLen = aMediaDescr.getLength(); + aMediaDescr.realloc( nOldLen + 1 ); + auto pMediaDescr = aMediaDescr.getArray(); + pMediaDescr[nOldLen].Name = "FilterName"; + pMediaDescr[ nOldLen ].Value = prop.Value; + break; + } + } + } + } + } + + return aFilterName; +} + +OUString MimeConfigurationHelper::UpdateMediaDescriptorWithFilterName( + uno::Sequence< beans::PropertyValue >& aMediaDescr, + uno::Sequence< beans::NamedValue >& aObject ) +{ + OUString aDocName; + for ( const auto & nv : std::as_const(aObject) ) + if ( nv.Name == "ObjectDocumentServiceName" ) + { + nv.Value >>= aDocName; + break; + } + + OSL_ENSURE( !aDocName.isEmpty(), "The name must exist at this point!" ); + + + bool bNeedsAddition = true; + for ( sal_Int32 nMedInd = 0; nMedInd < aMediaDescr.getLength(); nMedInd++ ) + if ( aMediaDescr[nMedInd].Name == "DocumentService" ) + { + aMediaDescr.getArray()[nMedInd].Value <<= aDocName; + bNeedsAddition = false; + break; + } + + if ( bNeedsAddition ) + { + sal_Int32 nOldLen = aMediaDescr.getLength(); + aMediaDescr.realloc( nOldLen + 1 ); + auto pMediaDescr = aMediaDescr.getArray(); + pMediaDescr[nOldLen].Name = "DocumentService"; + pMediaDescr[nOldLen].Value <<= aDocName; + } + + return UpdateMediaDescriptorWithFilterName( aMediaDescr, true ); +} + +#ifdef _WIN32 + +SfxFilterFlags MimeConfigurationHelper::GetFilterFlags( const OUString& aFilterName ) +{ + SfxFilterFlags nFlags = SfxFilterFlags::NONE; + try + { + if ( !aFilterName.isEmpty() ) + { + uno::Reference< container::XNameAccess > xFilterFactory( + GetFilterFactory(), + uno::UNO_SET_THROW ); + + uno::Any aFilterAny = xFilterFactory->getByName( aFilterName ); + uno::Sequence< beans::PropertyValue > aData; + if ( aFilterAny >>= aData ) + { + SequenceAsHashMap aFilterHM( aData ); + nFlags = static_cast<SfxFilterFlags>(aFilterHM.getUnpackedValueOrDefault( "Flags", sal_Int32(0) )); + } + } + } catch( uno::Exception& ) + {} + + return nFlags; +} + +bool MimeConfigurationHelper::AddFilterNameCheckOwnFile( + uno::Sequence< beans::PropertyValue >& aMediaDescr ) +{ + OUString aFilterName = UpdateMediaDescriptorWithFilterName( aMediaDescr, false ); + if ( !aFilterName.isEmpty() ) + { + SfxFilterFlags nFlags = GetFilterFlags( aFilterName ); + // check the OWN flag + return bool(nFlags & SfxFilterFlags::OWN); + } + + return false; +} + +#endif + +OUString MimeConfigurationHelper::GetDefaultFilterFromServiceName( const OUString& aServiceName, sal_Int32 nVersion ) +{ + OUString aResult; + + if ( !aServiceName.isEmpty() && nVersion ) + try + { + uno::Reference< container::XContainerQuery > xFilterQuery( + GetFilterFactory(), + uno::UNO_QUERY_THROW ); + + uno::Sequence< beans::NamedValue > aSearchRequest + { + { "DocumentService", css::uno::Any(aServiceName) }, + { "FileFormatVersion", css::uno::Any(nVersion) } + }; + + uno::Reference< container::XEnumeration > xFilterEnum = + xFilterQuery->createSubSetEnumerationByProperties( aSearchRequest ); + + // use the first filter that is found + if ( xFilterEnum.is() ) + while ( xFilterEnum->hasMoreElements() ) + { + uno::Sequence< beans::PropertyValue > aProps; + if ( xFilterEnum->nextElement() >>= aProps ) + { + SfxFilterFlags nFlags = SfxFilterFlags::NONE; + OUString sName; + for (const auto & rPropVal : aProps) + { + if (rPropVal.Name == "Flags") + { + sal_Int32 nTmp(0); + if (rPropVal.Value >>= nTmp) + nFlags = static_cast<SfxFilterFlags>(nTmp); + } + else if (rPropVal.Name == "Name") + rPropVal.Value >>= sName; + } + + // that should be import, export, own filter and not a template filter ( TemplatePath flag ) + SfxFilterFlags const nRequired = SfxFilterFlags::OWN + // fdo#78159 for OOoXML, there is code to convert + // to ODF in OCommonEmbeddedObject::store* + // so accept it even though there's no export + | (SOFFICE_FILEFORMAT_60 == nVersion ? SfxFilterFlags::NONE : SfxFilterFlags::EXPORT) + | SfxFilterFlags::IMPORT; + if ( ( ( nFlags & nRequired ) == nRequired ) && !( nFlags & SfxFilterFlags::TEMPLATEPATH ) ) + { + // if there are more than one filter the preferred one should be used + // if there is no preferred filter the first one will be used + if ( aResult.isEmpty() || ( nFlags & SfxFilterFlags::PREFERED ) ) + aResult = sName; + if ( nFlags & SfxFilterFlags::PREFERED ) + break; // the preferred filter was found + } + } + } + } + catch( uno::Exception& ) + {} + + return aResult; +} + + +OUString MimeConfigurationHelper::GetExportFilterFromImportFilter( const OUString& aImportFilterName ) +{ + OUString aExportFilterName; + + try + { + if ( !aImportFilterName.isEmpty() ) + { + uno::Reference< container::XNameAccess > xFilterFactory( + GetFilterFactory(), + uno::UNO_SET_THROW ); + + uno::Any aImpFilterAny = xFilterFactory->getByName( aImportFilterName ); + uno::Sequence< beans::PropertyValue > aImpData; + if ( aImpFilterAny >>= aImpData ) + { + SequenceAsHashMap aImpFilterHM( aImpData ); + SfxFilterFlags nFlags = static_cast<SfxFilterFlags>(aImpFilterHM.getUnpackedValueOrDefault( "Flags", sal_Int32(0) )); + + if ( !( nFlags & SfxFilterFlags::IMPORT ) ) + { + OSL_FAIL( "This is no import filter!" ); + throw uno::Exception("this is no import filter", nullptr); + } + + if ( nFlags & SfxFilterFlags::EXPORT ) + { + aExportFilterName = aImportFilterName; + } + else + { + OUString aDocumentServiceName = aImpFilterHM.getUnpackedValueOrDefault( "DocumentService", OUString() ); + OUString aTypeName = aImpFilterHM.getUnpackedValueOrDefault( "Type", OUString() ); + + OSL_ENSURE( !aDocumentServiceName.isEmpty() && !aTypeName.isEmpty(), "Incomplete filter data!" ); + if ( !(aDocumentServiceName.isEmpty() || aTypeName.isEmpty()) ) + { + uno::Sequence< beans::NamedValue > aSearchRequest + { + { "Type", css::uno::Any(aTypeName) }, + { "DocumentService", css::uno::Any(aDocumentServiceName) } + }; + + uno::Sequence< beans::PropertyValue > aExportFilterProps = SearchForFilter( + uno::Reference< container::XContainerQuery >( xFilterFactory, uno::UNO_QUERY_THROW ), + aSearchRequest, + SfxFilterFlags::EXPORT, + SfxFilterFlags::INTERNAL ); + + if ( aExportFilterProps.hasElements() ) + { + SequenceAsHashMap aExpPropsHM( aExportFilterProps ); + aExportFilterName = aExpPropsHM.getUnpackedValueOrDefault( "Name", OUString() ); + } + } + } + } + } + } + catch( uno::Exception& ) + {} + + return aExportFilterName; +} + + +// static +uno::Sequence< beans::PropertyValue > MimeConfigurationHelper::SearchForFilter( + const uno::Reference< container::XContainerQuery >& xFilterQuery, + const uno::Sequence< beans::NamedValue >& aSearchRequest, + SfxFilterFlags nMustFlags, + SfxFilterFlags nDontFlags ) +{ + uno::Sequence< beans::PropertyValue > aFilterProps; + uno::Reference< container::XEnumeration > xFilterEnum = + xFilterQuery->createSubSetEnumerationByProperties( aSearchRequest ); + + // the first default filter will be taken, + // if there is no filter with flag default the first acceptable filter will be taken + if ( xFilterEnum.is() ) + { + while ( xFilterEnum->hasMoreElements() ) + { + uno::Sequence< beans::PropertyValue > aProps; + if ( xFilterEnum->nextElement() >>= aProps ) + { + SequenceAsHashMap aPropsHM( aProps ); + SfxFilterFlags nFlags = static_cast<SfxFilterFlags>(aPropsHM.getUnpackedValueOrDefault("Flags", + sal_Int32(0) )); + if ( ( ( nFlags & nMustFlags ) == nMustFlags ) && !( nFlags & nDontFlags ) ) + { + if ( ( nFlags & SfxFilterFlags::DEFAULT ) == SfxFilterFlags::DEFAULT ) + { + aFilterProps = aProps; + break; + } + else if ( !aFilterProps.hasElements() ) + aFilterProps = aProps; + } + } + } + } + + return aFilterProps; +} + + +bool MimeConfigurationHelper::ClassIDsEqual( const uno::Sequence< sal_Int8 >& aClassID1, const uno::Sequence< sal_Int8 >& aClassID2 ) +{ + return aClassID1 == aClassID2; +} + + +uno::Sequence< sal_Int8 > MimeConfigurationHelper::GetSequenceClassID( sal_uInt32 n1, sal_uInt16 n2, sal_uInt16 n3, + sal_uInt8 b8, sal_uInt8 b9, sal_uInt8 b10, sal_uInt8 b11, + sal_uInt8 b12, sal_uInt8 b13, sal_uInt8 b14, sal_uInt8 b15 ) +{ + uno::Sequence< sal_Int8 > aResult{ /* [ 0] */ static_cast<sal_Int8>( n1 >> 24 ), + /* [ 1] */ static_cast<sal_Int8>( ( n1 << 8 ) >> 24 ), + /* [ 2] */ static_cast<sal_Int8>( ( n1 << 16 ) >> 24 ), + /* [ 3] */ static_cast<sal_Int8>( ( n1 << 24 ) >> 24 ), + /* [ 4] */ static_cast<sal_Int8>( n2 >> 8 ), + /* [ 5] */ static_cast<sal_Int8>( ( n2 << 8 ) >> 8 ), + /* [ 6] */ static_cast<sal_Int8>( n3 >> 8 ), + /* [ 7] */ static_cast<sal_Int8>( ( n3 << 8 ) >> 8 ), + /* [ 8] */ static_cast<sal_Int8>( b8 ), + /* [ 9] */ static_cast<sal_Int8>( b9 ), + /* [10] */ static_cast<sal_Int8>( b10 ), + /* [11] */ static_cast<sal_Int8>( b11 ), + /* [12] */ static_cast<sal_Int8>( b12 ), + /* [13] */ static_cast<sal_Int8>( b13 ), + /* [14] */ static_cast<sal_Int8>( b14 ), + /* [15] */ static_cast<sal_Int8>( b15 ) }; + + return aResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/namedvaluecollection.cxx b/comphelper/source/misc/namedvaluecollection.cxx new file mode 100644 index 000000000..11ef15b30 --- /dev/null +++ b/comphelper/source/misc/namedvaluecollection.cxx @@ -0,0 +1,308 @@ +/* -*- 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/namedvaluecollection.hxx> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/beans/PropertyState.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <sal/log.hxx> + +#include <algorithm> +#include <unordered_map> + +namespace comphelper +{ + + + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::beans::PropertyValue; + using ::com::sun::star::beans::NamedValue; + using ::com::sun::star::uno::Type; + using ::com::sun::star::uno::cpp_acquire; + using ::com::sun::star::uno::cpp_release; + using ::com::sun::star::uno::cpp_queryInterface; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::beans::PropertyState_DIRECT_VALUE; + + NamedValueCollection::NamedValueCollection( const Any& _rElements ) + { + impl_assign( _rElements ); + } + + + NamedValueCollection::NamedValueCollection( const Sequence< Any >& _rArguments ) + { + impl_assign( _rArguments ); + } + + + NamedValueCollection::NamedValueCollection( const Sequence< PropertyValue >& _rArguments ) + { + impl_assign( _rArguments ); + } + + + NamedValueCollection::NamedValueCollection( const Sequence< NamedValue >& _rArguments ) + { + impl_assign( _rArguments ); + } + + + bool NamedValueCollection::canExtractFrom( css::uno::Any const & i_value ) + { + Type const & aValueType = i_value.getValueType(); + return aValueType.equals( ::cppu::UnoType< PropertyValue >::get() ) + || aValueType.equals( ::cppu::UnoType< NamedValue >::get() ) + || aValueType.equals( ::cppu::UnoType< Sequence< PropertyValue > >::get() ) + || aValueType.equals( ::cppu::UnoType< Sequence< NamedValue > >::get() ); + } + + + NamedValueCollection& NamedValueCollection::merge( const NamedValueCollection& _rAdditionalValues, bool _bOverwriteExisting ) + { + for (auto const& value : _rAdditionalValues.maValues) + { + if ( _bOverwriteExisting || !impl_has( value.first ) ) + impl_put( value.first, value.second ); + } + + return *this; + } + + + size_t NamedValueCollection::size() const + { + return maValues.size(); + } + + + bool NamedValueCollection::empty() const + { + return maValues.empty(); + } + + + std::vector< OUString > NamedValueCollection::getNames() const + { + std::vector< OUString > aNames; + for (auto const& value : maValues) + { + aNames.push_back( value.first ); + } + return aNames; + } + + + void NamedValueCollection::impl_assign( const Any& i_rWrappedElements ) + { + Sequence< NamedValue > aNamedValues; + Sequence< PropertyValue > aPropertyValues; + NamedValue aNamedValue; + PropertyValue aPropertyValue; + + if ( i_rWrappedElements >>= aNamedValues ) + impl_assign( aNamedValues ); + else if ( i_rWrappedElements >>= aPropertyValues ) + impl_assign( aPropertyValues ); + else if ( i_rWrappedElements >>= aNamedValue ) + impl_assign( Sequence< NamedValue >( &aNamedValue, 1 ) ); + else if ( i_rWrappedElements >>= aPropertyValue ) + impl_assign( Sequence< PropertyValue >( &aPropertyValue, 1 ) ); + else + SAL_WARN_IF( i_rWrappedElements.hasValue(), "comphelper", "NamedValueCollection::impl_assign(Any): unsupported type!" ); + } + + + void NamedValueCollection::impl_assign( const Sequence< Any >& _rArguments ) + { + maValues.clear(); + + PropertyValue aPropertyValue; + NamedValue aNamedValue; + + for ( auto const & argument : _rArguments ) + { + if ( argument >>= aPropertyValue ) + maValues[ aPropertyValue.Name ] = aPropertyValue.Value; + else if ( argument >>= aNamedValue ) + maValues[ aNamedValue.Name ] = aNamedValue.Value; + else + { + SAL_WARN_IF( + argument.hasValue(), "comphelper", + ("NamedValueCollection::impl_assign: encountered a value" + " type which I cannot handle: " + + argument.getValueTypeName())); + } + } + } + + + void NamedValueCollection::impl_assign( const Sequence< PropertyValue >& _rArguments ) + { + maValues.clear(); + + for ( auto const & argument : _rArguments ) + maValues[ argument.Name ] = argument.Value; + } + + + void NamedValueCollection::impl_assign( const Sequence< NamedValue >& _rArguments ) + { + maValues.clear(); + + for ( auto const & argument : _rArguments ) + maValues[ argument.Name ] = argument.Value; + } + + + bool NamedValueCollection::get_ensureType( const OUString& _rValueName, void* _pValueLocation, const Type& _rExpectedValueType ) const + { + auto pos = maValues.find( _rValueName ); + if ( pos == maValues.end() ) + // argument does not exist + return false; + + if ( uno_type_assignData( + _pValueLocation, _rExpectedValueType.getTypeLibType(), + const_cast< void* >( pos->second.getValue() ), pos->second.getValueType().getTypeLibType(), + reinterpret_cast< uno_QueryInterfaceFunc >( cpp_queryInterface ), + reinterpret_cast< uno_AcquireFunc >( cpp_acquire ), + reinterpret_cast< uno_ReleaseFunc >( cpp_release ) + ) ) + // argument exists, and could be extracted + return true; + + // argument exists, but is of wrong type + throw IllegalArgumentException( + "Invalid value type for '" + _rValueName + + "'.\nExpected: " + _rExpectedValueType.getTypeName() + + "\nFound: " + pos->second.getValueType().getTypeName(), + nullptr, 0 ); + } + + // static + bool NamedValueCollection::get_ensureType( const css::uno::Sequence<css::beans::PropertyValue>& rPropSeq, + std::u16string_view _rValueName, void* _pValueLocation, const Type& _rExpectedValueType ) + { + for (const css::beans::PropertyValue& rPropVal : rPropSeq) + { + if (rPropVal.Name == _rValueName) + { + if ( uno_type_assignData( + _pValueLocation, _rExpectedValueType.getTypeLibType(), + const_cast< void* >( rPropVal.Value.getValue() ), rPropVal.Value.getValueType().getTypeLibType(), + reinterpret_cast< uno_QueryInterfaceFunc >( cpp_queryInterface ), + reinterpret_cast< uno_AcquireFunc >( cpp_acquire ), + reinterpret_cast< uno_ReleaseFunc >( cpp_release ) + ) ) + // argument exists, and could be extracted + return true; + + // argument exists, but is of wrong type + throw IllegalArgumentException( + OUString::Concat("Invalid value type for '") + _rValueName + + "'.\nExpected: " + _rExpectedValueType.getTypeName() + + "\nFound: " + rPropVal.Value.getValueType().getTypeName(), + nullptr, 0 ); + } + } + // argument does not exist + return false; + } + + // static + const css::uno::Any& NamedValueCollection::get( const css::uno::Sequence<css::beans::PropertyValue>& rPropSeq, + std::u16string_view _rValueName ) + { + static const Any theEmptyDefault; + for (const css::beans::PropertyValue& rPropVal : rPropSeq) + { + if (rPropVal.Name == _rValueName) + { + return rPropVal.Value; + } + } + return theEmptyDefault; + } + + const Any& NamedValueCollection::impl_get( const OUString& _rValueName ) const + { + static const Any theEmptyDefault; + auto pos = maValues.find( _rValueName ); + if ( pos != maValues.end() ) + return pos->second; + + return theEmptyDefault; + } + + + bool NamedValueCollection::impl_has( const OUString& _rValueName ) const + { + auto pos = maValues.find( _rValueName ); + return ( pos != maValues.end() ); + } + + + bool NamedValueCollection::impl_put( const OUString& _rValueName, const Any& _rValue ) + { + bool bHas = impl_has( _rValueName ); + maValues[ _rValueName ] = _rValue; + return bHas; + } + + + bool NamedValueCollection::impl_remove( const OUString& _rValueName ) + { + auto pos = maValues.find( _rValueName ); + if ( pos == maValues.end() ) + return false; + maValues.erase( pos ); + return true; + } + + + sal_Int32 NamedValueCollection::operator >>= ( Sequence< PropertyValue >& _out_rValues ) const + { + _out_rValues.realloc( maValues.size() ); + std::transform( maValues.begin(), maValues.end(), _out_rValues.getArray(), + [](const std::pair< OUString, css::uno::Any >& _rValue) + { return PropertyValue( _rValue.first, 0, _rValue.second, PropertyState_DIRECT_VALUE ); } ); + return _out_rValues.getLength(); + } + + + sal_Int32 NamedValueCollection::operator >>= ( Sequence< NamedValue >& _out_rValues ) const + { + _out_rValues.realloc( maValues.size() ); + std::transform( maValues.begin(), maValues.end(), _out_rValues.getArray(), + [](const std::pair< OUString, css::uno::Any >& _rValue) + { return NamedValue( _rValue.first, _rValue.second ); } ); + return _out_rValues.getLength(); + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/numberedcollection.cxx b/comphelper/source/misc/numberedcollection.cxx new file mode 100644 index 000000000..80d33776c --- /dev/null +++ b/comphelper/source/misc/numberedcollection.cxx @@ -0,0 +1,213 @@ +/* -*- 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 <algorithm> +#include <comphelper/numberedcollection.hxx> +#include <com/sun/star/frame/UntitledNumbersConst.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +namespace comphelper{ + +constexpr OUStringLiteral ERRMSG_INVALID_COMPONENT_PARAM = u"NULL as component reference not allowed."; + + +NumberedCollection::NumberedCollection() +{ +} + + +NumberedCollection::~NumberedCollection() +{ +} + + +void NumberedCollection::setOwner(const css::uno::Reference< css::uno::XInterface >& xOwner) +{ + // SYNCHRONIZED -> + std::scoped_lock aLock(m_aMutex); + + m_xOwner = xOwner; + + // <- SYNCHRONIZED +} + + +void NumberedCollection::setUntitledPrefix(const OUString& sPrefix) +{ + // SYNCHRONIZED -> + std::scoped_lock aLock(m_aMutex); + + m_sUntitledPrefix = sPrefix; + + // <- SYNCHRONIZED +} + + +::sal_Int32 SAL_CALL NumberedCollection::leaseNumber(const css::uno::Reference< css::uno::XInterface >& xComponent) +{ + // SYNCHRONIZED -> + std::scoped_lock aLock(m_aMutex); + + if ( ! xComponent.is ()) + throw css::lang::IllegalArgumentException(ERRMSG_INVALID_COMPONENT_PARAM, m_xOwner.get(), 1); + + sal_IntPtr pComponent = reinterpret_cast<sal_IntPtr>( xComponent.get() ); + TNumberedItemHash::const_iterator pIt = m_lComponents.find (pComponent); + + // a) component already exists - return its number directly + if (pIt != m_lComponents.end()) + return pIt->second.nNumber; + + // b) component must be added new to this container + + // b1) collection is full - no further components possible + // -> return INVALID_NUMBER + ::sal_Int32 nFreeNumber = impl_searchFreeNumber(); + if (nFreeNumber == css::frame::UntitledNumbersConst::INVALID_NUMBER) + return css::frame::UntitledNumbersConst::INVALID_NUMBER; + + // b2) add component to collection and return its number + TNumberedItem aItem; + aItem.xItem = css::uno::WeakReference< css::uno::XInterface >(xComponent); + aItem.nNumber = nFreeNumber; + m_lComponents[pComponent] = aItem; + + return nFreeNumber; + + // <- SYNCHRONIZED +} + + +void SAL_CALL NumberedCollection::releaseNumber(::sal_Int32 nNumber) +{ + // SYNCHRONIZED -> + std::scoped_lock aLock(m_aMutex); + + if (nNumber == css::frame::UntitledNumbersConst::INVALID_NUMBER) + throw css::lang::IllegalArgumentException ("Special value INVALID_NUMBER not allowed as input parameter.", m_xOwner.get(), 1); + + TDeadItemList lDeadItems; + TNumberedItemHash::iterator pComponent; + + for ( pComponent = m_lComponents.begin (); + pComponent != m_lComponents.end (); + ++pComponent ) + { + const TNumberedItem& rItem = pComponent->second; + const css::uno::Reference< css::uno::XInterface > xItem = rItem.xItem.get(); + + if ( ! xItem.is ()) + { + lDeadItems.push_back(pComponent->first); + continue; + } + + if (rItem.nNumber == nNumber) + { + m_lComponents.erase (pComponent); + break; + } + } + + impl_cleanUpDeadItems(m_lComponents, lDeadItems); + + // <- SYNCHRONIZED +} + + +void SAL_CALL NumberedCollection::releaseNumberForComponent(const css::uno::Reference< css::uno::XInterface >& xComponent) +{ + // SYNCHRONIZED -> + std::scoped_lock aLock(m_aMutex); + + if ( ! xComponent.is ()) + throw css::lang::IllegalArgumentException(ERRMSG_INVALID_COMPONENT_PARAM, m_xOwner.get(), 1); + + sal_IntPtr pComponent = reinterpret_cast<sal_IntPtr>( xComponent.get() ); + TNumberedItemHash::iterator pIt = m_lComponents.find (pComponent); + + // a) component exists and will be removed + if (pIt != m_lComponents.end()) + m_lComponents.erase(pIt); + + // else + // b) component does not exists - nothing todo here (ignore request!) + + // <- SYNCHRONIZED +} + + +OUString SAL_CALL NumberedCollection::getUntitledPrefix() +{ + // SYNCHRONIZED -> + std::scoped_lock aLock(m_aMutex); + + return m_sUntitledPrefix; + + // <- SYNCHRONIZED +} + + +/** create an ordered list of all possible numbers ... + e.g. {1,2,3,...,N} Max size of these list will be + current size of component list + 1 . + + "+1" ... because in case all numbers in range 1..n + are in use we need a new number n+1 :-) + + Every item which is already used as unique number + will be removed. At the end a list of e.g. {3,6,...,M} + exists where the first item represent the lowest free + number (in this example 3). + */ +::sal_Int32 NumberedCollection::impl_searchFreeNumber () +{ + // create bitset, where each position represents one possible number. + std::vector<bool> aUsedNumbers((m_lComponents.size() * 2) + 1, false); + + for (const auto& rPair : m_lComponents) + { + // numbers start at 1 + sal_Int32 pos = rPair.second.nNumber - 1; + if (pos >= static_cast<sal_Int32>(aUsedNumbers.size())) + aUsedNumbers.resize(pos * 2, false); // should be rare + aUsedNumbers[pos] = true; + } + + // a) non free numbers ... return INVALID_NUMBER + auto it = std::find(aUsedNumbers.begin(), aUsedNumbers.end(), false); + if (it == aUsedNumbers.end()) + return css::frame::UntitledNumbersConst::INVALID_NUMBER; + + // b) return first free number + return it - aUsedNumbers.begin() + 1; +} + +void NumberedCollection::impl_cleanUpDeadItems ( TNumberedItemHash& lItems , + const TDeadItemList& lDeadItems) +{ + for (const sal_IntPtr& rDeadItem : lDeadItems) + { + lItems.erase(rDeadItem); + } +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/numbers.cxx b/comphelper/source/misc/numbers.cxx new file mode 100644 index 000000000..f3b609392 --- /dev/null +++ b/comphelper/source/misc/numbers.cxx @@ -0,0 +1,120 @@ +/* -*- 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/numbers.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <com/sun/star/util/NumberFormat.hpp> +#include <com/sun/star/util/XNumberFormatter.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + + +namespace comphelper +{ + +sal_Int16 getNumberFormatType(const css::uno::Reference<css::util::XNumberFormats>& xFormats, sal_Int32 nKey) +{ + sal_Int16 nReturn(css::util::NumberFormat::UNDEFINED); + if (xFormats.is()) + { + try + { + css::uno::Reference<css::beans::XPropertySet> xFormat(xFormats->getByKey(nKey)); + if (xFormat.is()) + xFormat->getPropertyValue("Type") >>= nReturn; + } + catch(...) + { + SAL_WARN("comphelper", "getNumberFormatType : invalid key! (maybe created with another formatter ?)"); + } + } + return nReturn; +} + + +sal_Int16 getNumberFormatType(const css::uno::Reference<css::util::XNumberFormatter>& xFormatter, sal_Int32 nKey) +{ + OSL_ENSURE(xFormatter.is(), "getNumberFormatType : the formatter isn't valid !"); + css::uno::Reference<css::util::XNumberFormatsSupplier> xSupplier( xFormatter->getNumberFormatsSupplier()); + OSL_ENSURE(xSupplier.is(), "getNumberFormatType : the formatter doesn't implement a supplier !"); + css::uno::Reference<css::util::XNumberFormats> xFormats( xSupplier->getNumberFormats()); + return getNumberFormatType(xFormats, nKey); +} + + +css::uno::Any getNumberFormatDecimals(const css::uno::Reference<css::util::XNumberFormats>& xFormats, sal_Int32 nKey) +{ + if (xFormats.is()) + { + try + { + css::uno::Reference<css::beans::XPropertySet> xFormat( xFormats->getByKey(nKey)); + if (xFormat.is()) + { + return xFormat->getPropertyValue( "Decimals" ); + } + } + catch(...) + { + SAL_WARN("comphelper", "getNumberFormatDecimals : invalid key! (may be created with another formatter ?)"); + } + } + return css::uno::Any(sal_Int16(0)); +} + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::beans; + + +Any getNumberFormatProperty( const Reference< XNumberFormatter >& _rxFormatter, sal_Int32 _nKey, const OUString& _rPropertyName ) +{ + Any aReturn; + + OSL_ENSURE( _rxFormatter.is() && !_rPropertyName.isEmpty(), "getNumberFormatProperty: invalid arguments!" ); + try + { + Reference< XNumberFormatsSupplier > xSupplier; + Reference< XNumberFormats > xFormats; + Reference< XPropertySet > xFormatProperties; + + if ( _rxFormatter.is() ) + xSupplier = _rxFormatter->getNumberFormatsSupplier(); + if ( xSupplier.is() ) + xFormats = xSupplier->getNumberFormats(); + if ( xFormats.is() ) + xFormatProperties = xFormats->getByKey( _nKey ); + + if ( xFormatProperties.is() ) + aReturn = xFormatProperties->getPropertyValue( _rPropertyName ); + } + catch( const Exception& ) + { + OSL_FAIL( "::getNumberFormatProperty: caught an exception (did you create the key with another formatter?)!" ); + } + + return aReturn; +} + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/officerestartmanager.cxx b/comphelper/source/misc/officerestartmanager.cxx new file mode 100644 index 000000000..39c61a4e5 --- /dev/null +++ b/comphelper/source/misc/officerestartmanager.cxx @@ -0,0 +1,157 @@ +/* -*- 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/XMultiComponentFactory.hpp> +#include <com/sun/star/awt/XRequestCallback.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <cppuhelper/supportsservice.hxx> +#include "officerestartmanager.hxx" + +using namespace ::com::sun::star; + +namespace comphelper +{ + +// XRestartManager + +void SAL_CALL OOfficeRestartManager::requestRestart( const uno::Reference< task::XInteractionHandler >& /* xInteractionHandler */ ) +{ + if ( !m_xContext.is() ) + throw uno::RuntimeException(); + + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // if the restart already running there is no need to trigger it again + if ( m_bRestartRequested ) + return; + + m_bRestartRequested = true; + + // the office is still not initialized, no need to terminate, changing the state is enough + if ( !m_bOfficeInitialized ) + return; + } + + // TODO: use InteractionHandler to report errors + try + { + // register itself as a job that should be executed asynchronously + uno::Reference< lang::XMultiComponentFactory > xFactory( m_xContext->getServiceManager(), uno::UNO_SET_THROW ); + + uno::Reference< awt::XRequestCallback > xRequestCallback( + xFactory->createInstanceWithContext( + "com.sun.star.awt.AsyncCallback", + m_xContext ), + uno::UNO_QUERY_THROW ); + + xRequestCallback->addCallback( this, uno::Any() ); + } + catch ( uno::Exception& ) + { + // the try to request restart has failed + m_bRestartRequested = false; + } +} + + +sal_Bool SAL_CALL OOfficeRestartManager::isRestartRequested( sal_Bool bOfficeInitialized ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( bOfficeInitialized && !m_bOfficeInitialized ) + m_bOfficeInitialized = bOfficeInitialized; + + return m_bRestartRequested; +} + +// XCallback + +void SAL_CALL OOfficeRestartManager::notify( const uno::Any& /* aData */ ) +{ + try + { + bool bSuccess = false; + + if ( m_xContext.is() ) + { + uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create(m_xContext); + + // Turn Quickstarter veto off + uno::Reference< beans::XPropertySet > xPropertySet( xDesktop, uno::UNO_QUERY_THROW ); + OUString aVetoPropName( "SuspendQuickstartVeto" ); + uno::Any aValue; + aValue <<= true; + xPropertySet->setPropertyValue( aVetoPropName, aValue ); + + try + { + bSuccess = xDesktop->terminate(); + } catch( uno::Exception& ) + {} + + if ( !bSuccess ) + { + aValue <<= false; + xPropertySet->setPropertyValue( aVetoPropName, aValue ); + } + } + + if ( !bSuccess ) + m_bRestartRequested = false; + } + catch( uno::Exception& ) + { + // the try to restart has failed + m_bRestartRequested = false; + } +} + +// XServiceInfo + +OUString SAL_CALL OOfficeRestartManager::getImplementationName() +{ + return "com.sun.star.comp.task.OfficeRestartManager"; +} + +sal_Bool SAL_CALL OOfficeRestartManager::supportsService( const OUString& aServiceName ) +{ + return cppu::supportsService(this, aServiceName); +} + +uno::Sequence< OUString > SAL_CALL OOfficeRestartManager::getSupportedServiceNames() +{ + return { "com.sun.star.comp.task.OfficeRestartManager" }; +} + +} // namespace comphelper + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_task_OfficeRestartManager( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new comphelper::OOfficeRestartManager(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/officerestartmanager.hxx b/comphelper/source/misc/officerestartmanager.hxx new file mode 100644 index 000000000..bc95668b9 --- /dev/null +++ b/comphelper/source/misc/officerestartmanager.hxx @@ -0,0 +1,67 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/task/XRestartManager.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/awt/XCallback.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <osl/mutex.hxx> +#include <cppuhelper/implbase.hxx> +#include <utility> + +namespace comphelper +{ + +class OOfficeRestartManager : public ::cppu::WeakImplHelper< css::task::XRestartManager + , css::awt::XCallback + , css::lang::XServiceInfo > +{ + ::osl::Mutex m_aMutex; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + bool m_bOfficeInitialized; + bool m_bRestartRequested; + +public: + explicit OOfficeRestartManager( css::uno::Reference< css::uno::XComponentContext > xContext ) + : m_xContext(std::move( xContext )) + , m_bOfficeInitialized( false ) + , m_bRestartRequested( false ) + {} + +// XRestartManager + virtual void SAL_CALL requestRestart( const css::uno::Reference< css::task::XInteractionHandler >& xInteractionHandler ) override; + virtual sal_Bool SAL_CALL isRestartRequested( sal_Bool bInitialized ) override; + +// XCallback + virtual void SAL_CALL notify( const css::uno::Any& aData ) 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; + +}; + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/proxyaggregation.cxx b/comphelper/source/misc/proxyaggregation.cxx new file mode 100644 index 000000000..db580bea4 --- /dev/null +++ b/comphelper/source/misc/proxyaggregation.cxx @@ -0,0 +1,243 @@ +/* -*- 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 <cassert> + +#include <comphelper/proxyaggregation.hxx> +#include <com/sun/star/reflection/ProxyFactory.hpp> + + +namespace comphelper +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::reflection; + + OProxyAggregation::OProxyAggregation( const Reference< XComponentContext >& _rxContext ) + :m_xContext( _rxContext ) + { + } + + + void OProxyAggregation::baseAggregateProxyFor( const Reference< XInterface >& _rxComponent, oslInterlockedCount& _rRefCount, + ::cppu::OWeakObject& _rDelegator ) + { + // first a factory for the proxy + Reference< XProxyFactory > xFactory = ProxyFactory::create( m_xContext ); + + // then the proxy itself + { // i36686 OJ: achieve the destruction of the temporary -> otherwise it leads to _rRefCount -= 2 + m_xProxyAggregate = xFactory->createProxy( _rxComponent ); + } + if ( m_xProxyAggregate.is() ) + m_xProxyAggregate->queryAggregation( cppu::UnoType<decltype(m_xProxyTypeAccess)>::get() ) >>= m_xProxyTypeAccess; + + // aggregate the proxy + osl_atomic_increment( &_rRefCount ); + if ( m_xProxyAggregate.is() ) + { + // At this point in time, the proxy has a ref count of exactly two - in m_xControlContextProxy, + // and in m_xProxyTypeAccess. + // Remember to _not_ reset these members unless the delegator of the proxy has been reset, too! + m_xProxyAggregate->setDelegator( _rDelegator ); + } + osl_atomic_decrement( &_rRefCount ); + } + + + Any SAL_CALL OProxyAggregation::queryAggregation( const Type& _rType ) + { + return m_xProxyAggregate.is() ? m_xProxyAggregate->queryAggregation( _rType ) : Any(); + } + + + Sequence< Type > SAL_CALL OProxyAggregation::getTypes( ) + { + Sequence< Type > aTypes; + if ( m_xProxyAggregate.is() ) + { + if ( m_xProxyTypeAccess.is() ) + aTypes = m_xProxyTypeAccess->getTypes(); + } + return aTypes; + } + + + OProxyAggregation::~OProxyAggregation() + { + if ( m_xProxyAggregate.is() ) + m_xProxyAggregate->setDelegator( nullptr ); + m_xProxyAggregate.clear(); + m_xProxyTypeAccess.clear(); + // this should remove the _two_only_ "real" references (means not delegated to + // ourself) to this proxy, and thus delete it + } + + OComponentProxyAggregationHelper::OComponentProxyAggregationHelper( const Reference< XComponentContext >& _rxContext, + ::cppu::OBroadcastHelper& _rBHelper ) + :OProxyAggregation( _rxContext ) + ,m_rBHelper( _rBHelper ) + { + OSL_ENSURE( _rxContext.is(), "OComponentProxyAggregationHelper::OComponentProxyAggregationHelper: invalid arguments!" ); + } + + + void OComponentProxyAggregationHelper::componentAggregateProxyFor( + const Reference< XComponent >& _rxComponent, oslInterlockedCount& _rRefCount, + ::cppu::OWeakObject& _rDelegator ) + { + OSL_ENSURE( _rxComponent.is(), "OComponentProxyAggregationHelper::componentAggregateProxyFor: invalid inner component!" ); + m_xInner = _rxComponent; + + // aggregate a proxy for the object + baseAggregateProxyFor( m_xInner, _rRefCount, _rDelegator ); + + // add as event listener to the inner context, because we want to be notified of disposals + osl_atomic_increment( &_rRefCount ); + { + if ( m_xInner.is() ) + m_xInner->addEventListener( this ); + } + osl_atomic_decrement( &_rRefCount ); + } + + + Any SAL_CALL OComponentProxyAggregationHelper::queryInterface( const Type& _rType ) + { + Any aReturn( BASE::queryInterface( _rType ) ); + if ( !aReturn.hasValue() ) + aReturn = OProxyAggregation::queryAggregation( _rType ); + return aReturn; + } + + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OComponentProxyAggregationHelper, BASE, OProxyAggregation ) + + + OComponentProxyAggregationHelper::~OComponentProxyAggregationHelper( ) + { + OSL_ENSURE( m_rBHelper.bDisposed, "OComponentProxyAggregationHelper::~OComponentProxyAggregationHelper: you should dispose your derived class in the dtor, if necessary!" ); + // if this asserts, add the following to your derived class dtor: + + // if ( !m_rBHelper.bDisposed ) + // { + // acquire(); // to prevent duplicate dtor calls + // dispose(); + // } + + m_xInner.clear(); + } + + + void SAL_CALL OComponentProxyAggregationHelper::disposing( const EventObject& _rSource ) + { + if ( _rSource.Source == m_xInner ) + { // it's our inner context which is dying -> dispose ourself + if ( !m_rBHelper.bDisposed && !m_rBHelper.bInDispose ) + { // (if necessary only, of course) + dispose(); + } + } + } + + + void SAL_CALL OComponentProxyAggregationHelper::dispose() + { + ::osl::MutexGuard aGuard( m_rBHelper.rMutex ); + + // dispose our inner context + // before we do this, remove ourself as listener - else in disposing( EventObject ), we + // would dispose ourself a second time + if ( m_xInner.is() ) + { + m_xInner->removeEventListener( this ); + m_xInner->dispose(); + m_xInner.clear(); + } + } + + OComponentProxyAggregation::OComponentProxyAggregation( const Reference< XComponentContext >& _rxContext, + const Reference< XComponent >& _rxComponent ) + :WeakComponentImplHelperBase( m_aMutex ) + ,OComponentProxyAggregationHelper( _rxContext, rBHelper ) + { + OSL_ENSURE( _rxComponent.is(), "OComponentProxyAggregation::OComponentProxyAggregation: accessible is no XComponent!" ); + if ( _rxComponent.is() ) + componentAggregateProxyFor( _rxComponent, m_refCount, *this ); + } + + + OComponentProxyAggregation::~OComponentProxyAggregation() + { + if ( !rBHelper.bDisposed ) + { + acquire(); // to prevent duplicate dtor calls + dispose(); + } + } + + + IMPLEMENT_FORWARD_XINTERFACE2( OComponentProxyAggregation, WeakComponentImplHelperBase, OComponentProxyAggregationHelper ) + + + IMPLEMENT_GET_IMPLEMENTATION_ID( OComponentProxyAggregation ) + + + Sequence< Type > SAL_CALL OComponentProxyAggregation::getTypes( ) + { + return comphelper::concatSequences( + OComponentProxyAggregationHelper::getTypes(), + // append XComponent, coming from WeakComponentImplHelperBase + Sequence { cppu::UnoType<XComponent>::get() }); + } + + + void SAL_CALL OComponentProxyAggregation::disposing( const EventObject& _rSource ) + { + // Simply disambiguate---this is necessary for MSVC to distinguish + // "disposing(EventObject)" from "disposing()"; but it is also a good + // place to check for recursive calls that would be caused by an object + // being registered as an XEventListener at itself (cf. rhbz#928568): + assert(_rSource.Source != static_cast< cppu::OWeakObject * >(this)); + OComponentProxyAggregationHelper::disposing( _rSource ); + } + + + void SAL_CALL OComponentProxyAggregation::disposing() + { + // call the dispose-functionality of the base, which will dispose our aggregated component + OComponentProxyAggregationHelper::dispose(); + } + + + void SAL_CALL OComponentProxyAggregation::dispose() + { + // simply disambiguate + WeakComponentImplHelperBase::dispose(); + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/random.cxx b/comphelper/source/misc/random.cxx new file mode 100644 index 000000000..96d466641 --- /dev/null +++ b/comphelper/source/misc/random.cxx @@ -0,0 +1,123 @@ +/* -*- 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/. + * + * Contributor(s): + * Copyright (C) 2012 Tino Kluge <tino.kluge@hrz.tu-chemnitz.de> + */ + +#include <comphelper/random.hxx> +#include <sal/log.hxx> +#include <assert.h> +#include <time.h> +#include <mutex> +#include <random> +#include <stdexcept> +#if defined HAVE_VALGRIND_HEADERS +#include <valgrind/memcheck.h> +#endif + +// this is nothing but a simple wrapper around +// the std::random generators + +namespace comphelper::rng +{ +// underlying random number generator +// std::mt19937 implements the Mersenne twister algorithm which +// is fast and has good statistical properties, it produces integers +// in the range of [0, 2^32-1] internally +// memory requirement: 625*sizeof(uint32_t) +// http://en.wikipedia.org/wiki/Mersenne_twister +#define STD_RNG_ALGO std::mt19937 + +namespace +{ +struct RandomNumberGenerator +{ + std::mutex mutex; + STD_RNG_ALGO global_rng; + RandomNumberGenerator() + { + // make RR easier to use, breaks easily without the RNG being repeatable + bool bRepeatable = (getenv("SAL_RAND_REPEATABLE") != nullptr) || (getenv("RR") != nullptr); + // valgrind on some platforms (e.g.Ubuntu16.04) does not support the new Intel RDRAND instructions, + // which leads to "Illegal Opcode" errors, so just turn off randomness. +#if defined HAVE_VALGRIND_HEADERS + if (RUNNING_ON_VALGRIND) + bRepeatable = true; +#endif + if (bRepeatable) + { + global_rng.seed(42); + return; + } + + try + { + std::random_device rd; + // initialises the state of the global random number generator + // should only be called once. + // (note, a few std::variate_generator<> (like normal) have their + // own state which would need a reset as well to guarantee identical + // sequence of numbers, e.g. via myrand.distribution().reset()) + global_rng.seed(rd() ^ time(nullptr)); + } + catch (std::runtime_error& e) + { + SAL_WARN("comphelper", "Using std::random_device failed: " << e.what()); + global_rng.seed(time(nullptr)); + } + } +}; + +RandomNumberGenerator& GetTheRandomNumberGenerator() +{ + static RandomNumberGenerator RANDOM; + return RANDOM; +} +} + +// uniform ints [a,b] distribution +int uniform_int_distribution(int a, int b) +{ + std::uniform_int_distribution<int> dist(a, b); + auto& gen = GetTheRandomNumberGenerator(); + std::scoped_lock<std::mutex> g(gen.mutex); + return dist(gen.global_rng); +} + +// uniform ints [a,b] distribution +unsigned int uniform_uint_distribution(unsigned int a, unsigned int b) +{ + std::uniform_int_distribution<unsigned int> dist(a, b); + auto& gen = GetTheRandomNumberGenerator(); + std::scoped_lock<std::mutex> g(gen.mutex); + return dist(gen.global_rng); +} + +// uniform size_t [a,b] distribution +size_t uniform_size_distribution(size_t a, size_t b) +{ + std::uniform_int_distribution<size_t> dist(a, b); + auto& gen = GetTheRandomNumberGenerator(); + std::scoped_lock<std::mutex> g(gen.mutex); + return dist(gen.global_rng); +} + +// uniform size_t [a,b) distribution +double uniform_real_distribution(double a, double b) +{ + assert(a < b); + std::uniform_real_distribution<double> dist(a, b); + auto& gen = GetTheRandomNumberGenerator(); + std::scoped_lock<std::mutex> g(gen.mutex); + return dist(gen.global_rng); +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/sequenceashashmap.cxx b/comphelper/source/misc/sequenceashashmap.cxx new file mode 100644 index 000000000..34a6a0c8a --- /dev/null +++ b/comphelper/source/misc/sequenceashashmap.cxx @@ -0,0 +1,378 @@ +/* -*- 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 <boost/property_tree/json_parser.hpp> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/reflection/XIdlField.hpp> +#include <com/sun/star/reflection/theCoreReflection.hpp> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysequence.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +using namespace com::sun::star; + +namespace +{ +uno::Any jsonToUnoAny(const boost::property_tree::ptree& aTree) +{ + uno::Any aAny; + uno::Any aValue; + sal_Int32 nFields; + uno::Reference<reflection::XIdlField> aField; + boost::property_tree::ptree aNodeNull, aNodeValue, aNodeField; + const std::string& rType = aTree.get<std::string>("type", ""); + const std::string& rValue = aTree.get<std::string>("value", ""); + uno::Sequence<uno::Reference<reflection::XIdlField>> aFields; + uno::Reference<reflection::XIdlClass> xIdlClass + = css::reflection::theCoreReflection::get(comphelper::getProcessComponentContext()) + ->forName(OUString::fromUtf8(rType.c_str())); + if (xIdlClass.is()) + { + uno::TypeClass aTypeClass = xIdlClass->getTypeClass(); + xIdlClass->createObject(aAny); + aFields = xIdlClass->getFields(); + nFields = aFields.getLength(); + aNodeValue = aTree.get_child("value", aNodeNull); + if (nFields > 0 && aNodeValue != aNodeNull) + { + for (sal_Int32 itField = 0; itField < nFields; ++itField) + { + aField = aFields[itField]; + aNodeField = aNodeValue.get_child(aField->getName().toUtf8().getStr(), aNodeNull); + if (aNodeField != aNodeNull) + { + aValue = jsonToUnoAny(aNodeField); + aField->set(aAny, aValue); + } + } + } + else if (!rValue.empty()) + { + if (aTypeClass == uno::TypeClass_VOID) + aAny.clear(); + else if (aTypeClass == uno::TypeClass_BYTE) + aAny <<= static_cast<sal_Int8>(o3tl::toInt32(rValue)); + else if (aTypeClass == uno::TypeClass_BOOLEAN) + aAny <<= OString(rValue.c_str()).toBoolean(); + else if (aTypeClass == uno::TypeClass_SHORT) + aAny <<= static_cast<sal_Int16>(o3tl::toInt32(rValue)); + else if (aTypeClass == uno::TypeClass_UNSIGNED_SHORT) + aAny <<= static_cast<sal_uInt16>(o3tl::toUInt32(rValue)); + else if (aTypeClass == uno::TypeClass_LONG) + aAny <<= o3tl::toInt32(rValue); + else if (aTypeClass == uno::TypeClass_UNSIGNED_LONG) + aAny <<= static_cast<sal_uInt32>(o3tl::toInt32(rValue)); + else if (aTypeClass == uno::TypeClass_FLOAT) + aAny <<= OString(rValue.c_str()).toFloat(); + else if (aTypeClass == uno::TypeClass_DOUBLE) + aAny <<= o3tl::toDouble(rValue); + else if (aTypeClass == uno::TypeClass_STRING) + aAny <<= OUString::fromUtf8(rValue.c_str()); + } + } + return aAny; +} +} + +namespace comphelper{ + +SequenceAsHashMap::SequenceAsHashMap() +{ +} + +SequenceAsHashMap::SequenceAsHashMap(const css::uno::Any& aSource) +{ + (*this) << aSource; +} + + +SequenceAsHashMap::SequenceAsHashMap(const css::uno::Sequence< css::uno::Any >& lSource) +{ + (*this) << lSource; +} + +SequenceAsHashMap::SequenceAsHashMap(const css::uno::Sequence< css::beans::PropertyValue >& lSource) +{ + (*this) << lSource; +} + +SequenceAsHashMap::SequenceAsHashMap(const css::uno::Sequence< css::beans::NamedValue >& lSource) +{ + (*this) << lSource; +} + +void SequenceAsHashMap::operator<<(const css::uno::Any& aSource) +{ + // An empty Any reset this instance! + if (!aSource.hasValue()) + { + clear(); + return; + } + + css::uno::Sequence< css::beans::NamedValue > lN; + if (aSource >>= lN) + { + (*this) << lN; + return; + } + + css::uno::Sequence< css::beans::PropertyValue > lP; + if (aSource >>= lP) + { + (*this) << lP; + return; + } + + throw css::lang::IllegalArgumentException( + "Any contains wrong type.", css::uno::Reference<css::uno::XInterface>(), + -1); +} + + +void SequenceAsHashMap::operator<<(const css::uno::Sequence< css::uno::Any >& lSource) +{ + sal_Int32 c = lSource.getLength(); + sal_Int32 i = 0; + + m_aMap.reserve(c); + for (i=0; i<c; ++i) + { + css::beans::PropertyValue lP; + if (lSource[i] >>= lP) + { + if ( + (lP.Name.isEmpty()) || + (!lP.Value.hasValue()) + ) + throw css::lang::IllegalArgumentException( + "PropertyValue struct contains no useful information.", + css::uno::Reference<css::uno::XInterface>(), -1); + (*this)[lP.Name] = lP.Value; + continue; + } + + css::beans::NamedValue lN; + if (lSource[i] >>= lN) + { + if ( + (lN.Name.isEmpty()) || + (!lN.Value.hasValue()) + ) + throw css::lang::IllegalArgumentException( + "NamedValue struct contains no useful information.", + css::uno::Reference<css::uno::XInterface>(), -1); + (*this)[lN.Name] = lN.Value; + continue; + } + + // ignore VOID Any ... but reject wrong filled ones! + if (lSource[i].hasValue()) + throw css::lang::IllegalArgumentException( + "Any contains wrong type.", + css::uno::Reference<css::uno::XInterface>(), -1); + } +} + +void SequenceAsHashMap::operator<<(const css::uno::Sequence< css::beans::PropertyValue >& lSource) +{ + clear(); + + sal_Int32 c = lSource.getLength(); + const css::beans::PropertyValue* pSource = lSource.getConstArray(); + + m_aMap.reserve(c); + for (sal_Int32 i=0; i<c; ++i) + (*this)[pSource[i].Name] = pSource[i].Value; +} + +void SequenceAsHashMap::operator<<(const css::uno::Sequence< css::beans::NamedValue >& lSource) +{ + clear(); + + sal_Int32 c = lSource.getLength(); + const css::beans::NamedValue* pSource = lSource.getConstArray(); + + m_aMap.reserve(c); + for (sal_Int32 i=0; i<c; ++i) + (*this)[pSource[i].Name] = pSource[i].Value; +} + +void SequenceAsHashMap::operator>>(css::uno::Sequence< css::beans::PropertyValue >& lDestination) const +{ + sal_Int32 c = static_cast<sal_Int32>(size()); + lDestination.realloc(c); + css::beans::PropertyValue* pDestination = lDestination.getArray(); + + sal_Int32 i = 0; + for (const_iterator pThis = begin(); + pThis != end() ; + ++pThis ) + { + pDestination[i].Name = pThis->first.maString; + pDestination[i].Value = pThis->second; + ++i; + } +} + +void SequenceAsHashMap::operator>>(css::uno::Sequence< css::beans::NamedValue >& lDestination) const +{ + sal_Int32 c = static_cast<sal_Int32>(size()); + lDestination.realloc(c); + css::beans::NamedValue* pDestination = lDestination.getArray(); + + sal_Int32 i = 0; + for (const_iterator pThis = begin(); + pThis != end() ; + ++pThis ) + { + pDestination[i].Name = pThis->first.maString; + pDestination[i].Value = pThis->second; + ++i; + } +} + +css::uno::Any SequenceAsHashMap::getAsConstAny(bool bAsPropertyValueList) const +{ + css::uno::Any aDestination; + if (bAsPropertyValueList) + aDestination <<= getAsConstPropertyValueList(); + else + aDestination <<= getAsConstNamedValueList(); + return aDestination; +} + +css::uno::Sequence< css::beans::NamedValue > SequenceAsHashMap::getAsConstNamedValueList() const +{ + css::uno::Sequence< css::beans::NamedValue > lReturn; + (*this) >> lReturn; + return lReturn; +} + +css::uno::Sequence< css::beans::PropertyValue > SequenceAsHashMap::getAsConstPropertyValueList() const +{ + css::uno::Sequence< css::beans::PropertyValue > lReturn; + (*this) >> lReturn; + return lReturn; +} + +bool SequenceAsHashMap::match(const SequenceAsHashMap& rCheck) const +{ + for (auto const& elem : rCheck) + { + const OUString& sCheckName = elem.first.maString; + const css::uno::Any& aCheckValue = elem.second; + const_iterator pFound = find(sCheckName); + + if (pFound == end()) + return false; + + const css::uno::Any& aFoundValue = pFound->second; + if (aFoundValue != aCheckValue) + return false; + } + + return true; +} + +void SequenceAsHashMap::update(const SequenceAsHashMap& rUpdate) +{ + m_aMap.reserve(std::max(size(), rUpdate.size())); + for (auto const& elem : rUpdate.m_aMap) + { + m_aMap[elem.first] = elem.second; + } +} + +std::vector<css::beans::PropertyValue> JsonToPropertyValues(const OString& rJson) +{ + std::vector<beans::PropertyValue> aArguments; + boost::property_tree::ptree aTree, aNodeNull, aNodeValue; + std::stringstream aStream(rJson.getStr()); + boost::property_tree::read_json(aStream, aTree); + + for (const auto& rPair : aTree) + { + const std::string& rType = rPair.second.get<std::string>("type", ""); + const std::string& rValue = rPair.second.get<std::string>("value", ""); + + beans::PropertyValue aValue; + aValue.Name = OUString::fromUtf8(rPair.first.c_str()); + if (rType == "string") + aValue.Value <<= OUString::fromUtf8(rValue.c_str()); + else if (rType == "boolean") + aValue.Value <<= OString(rValue.c_str()).toBoolean(); + else if (rType == "float") + aValue.Value <<= OString(rValue.c_str()).toFloat(); + else if (rType == "long") + aValue.Value <<= o3tl::toInt32(rValue); + else if (rType == "short") + aValue.Value <<= sal_Int16(o3tl::toInt32(rValue)); + else if (rType == "unsigned short") + aValue.Value <<= sal_uInt16(o3tl::toUInt32(rValue)); + else if (rType == "int64") + aValue.Value <<= o3tl::toInt64(rValue); + else if (rType == "int32") + aValue.Value <<= o3tl::toInt32(rValue); + else if (rType == "int16") + aValue.Value <<= sal_Int16(o3tl::toInt32(rValue)); + else if (rType == "uint64") + aValue.Value <<= OString(rValue.c_str()).toUInt64(); + else if (rType == "uint32") + aValue.Value <<= o3tl::toUInt32(rValue); + else if (rType == "uint16") + aValue.Value <<= sal_uInt16(o3tl::toUInt32(rValue)); + else if (rType == "[]byte") + { + aNodeValue = rPair.second.get_child("value", aNodeNull); + if (aNodeValue != aNodeNull && aNodeValue.size() == 0) + { + uno::Sequence<sal_Int8> aSeqByte(reinterpret_cast<const sal_Int8*>(rValue.c_str()), + rValue.size()); + aValue.Value <<= aSeqByte; + } + } + else if (rType == "[]any") + { + aNodeValue = rPair.second.get_child("value", aNodeNull); + if (aNodeValue != aNodeNull && !aNodeValue.empty()) + { + uno::Sequence<uno::Any> aSeq(aNodeValue.size()); + std::transform(aNodeValue.begin(), aNodeValue.end(), aSeq.getArray(), + [](const auto& rSeqPair) { return jsonToUnoAny(rSeqPair.second); }); + aValue.Value <<= aSeq; + } + } + else + SAL_WARN("comphelper", "JsonToPropertyValues: unhandled type '" << rType << "'"); + aArguments.push_back(aValue); + } + return aArguments; +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/servicedecl.cxx b/comphelper/source/misc/servicedecl.cxx new file mode 100644 index 000000000..2b16539d1 --- /dev/null +++ b/comphelper/source/misc/servicedecl.cxx @@ -0,0 +1,161 @@ +/* -*- 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/servicedecl.hxx> +#include <o3tl/string_view.hxx> +#include <rtl/string.hxx> +#include <cppuhelper/implbase.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <cassert> +#include <vector> + +using namespace com::sun::star; + +namespace comphelper::service_decl { + +const char cDelim = ';'; + +class ServiceDecl::Factory : + public cppu::WeakImplHelper<lang::XSingleComponentFactory, + lang::XServiceInfo> +{ +public: + explicit Factory( ServiceDecl const& rServiceDecl ) + : m_rServiceDecl(rServiceDecl) {} + // noncopyable + Factory(const Factory&) = delete; + const Factory& operator=(const Factory&) = delete; + + // XServiceInfo: + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( OUString const& name ) override; + virtual uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + // XSingleComponentFactory: + virtual uno::Reference<uno::XInterface> SAL_CALL createInstanceWithContext( + uno::Reference<uno::XComponentContext> const& xContext ) override; + virtual uno::Reference<uno::XInterface> SAL_CALL + createInstanceWithArgumentsAndContext( + uno::Sequence<uno::Any> const& args, + uno::Reference<uno::XComponentContext> const& xContext ) override; + +private: + virtual ~Factory() override; + + ServiceDecl const& m_rServiceDecl; +}; + +ServiceDecl::Factory::~Factory() +{ +} + +// XServiceInfo: +OUString ServiceDecl::Factory::getImplementationName() +{ + return m_rServiceDecl.getImplementationName(); +} + +sal_Bool ServiceDecl::Factory::supportsService( OUString const& name ) +{ + return m_rServiceDecl.supportsService(name); +} + +uno::Sequence<OUString> ServiceDecl::Factory::getSupportedServiceNames() +{ + return m_rServiceDecl.getSupportedServiceNames(); +} + +// XSingleComponentFactory: +uno::Reference<uno::XInterface> ServiceDecl::Factory::createInstanceWithContext( + uno::Reference<uno::XComponentContext> const& xContext ) +{ + return m_rServiceDecl.m_createFunc( + m_rServiceDecl, uno::Sequence<uno::Any>(), xContext ); +} + +uno::Reference<uno::XInterface> +ServiceDecl::Factory::createInstanceWithArgumentsAndContext( + uno::Sequence<uno::Any > const& args, + uno::Reference<uno::XComponentContext> const& xContext ) +{ + return m_rServiceDecl.m_createFunc( + m_rServiceDecl, args, xContext ); +} + +void * ServiceDecl::getFactory( char const* pImplName ) const +{ + if (rtl_str_compare(m_pImplName, pImplName) == 0) { + lang::XSingleComponentFactory * const pFac( new Factory(*this) ); + pFac->acquire(); + return pFac; + } + return nullptr; +} + +uno::Sequence<OUString> ServiceDecl::getSupportedServiceNames() const +{ + std::vector<OUString> vec; + + OString const str(m_pServiceNames); + sal_Int32 nIndex = 0; + do { + std::string_view const token( o3tl::getToken(str, 0, cDelim, nIndex ) ); + vec.emplace_back( token.data(), token.size(), + RTL_TEXTENCODING_ASCII_US ); + } + while (nIndex >= 0); + + return comphelper::containerToSequence(vec); +} + +bool ServiceDecl::supportsService( std::u16string_view name ) const +{ + OString const str(m_pServiceNames); + sal_Int32 nIndex = 0; + do { + std::string_view const token( o3tl::getToken(str, 0, cDelim, nIndex ) ); + if (o3tl::equalsAscii(name, token)) + return true; + } + while (nIndex >= 0); + return false; +} + +OUString ServiceDecl::getImplementationName() const +{ + return OUString::createFromAscii(m_pImplName); +} + +void* component_getFactoryHelper( const char* pImplName, + std::initializer_list<ServiceDecl const *> args ) +{ + for (auto const i: args) { + assert(i != nullptr); + void * fac = i->getFactory(pImplName); + if (fac != nullptr) { + return fac; + } + } + return nullptr; +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/sharedmutex.cxx b/comphelper/source/misc/sharedmutex.cxx new file mode 100644 index 000000000..58ae35df5 --- /dev/null +++ b/comphelper/source/misc/sharedmutex.cxx @@ -0,0 +1,42 @@ +/* -*- 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/sharedmutex.hxx> +#include <comphelper/refcountedmutex.hxx> + + +namespace comphelper +{ + + SharedMutex::SharedMutex() + :m_pMutexImpl( std::make_shared<::osl::Mutex >()) + { + } + + + RefCountedMutex::~RefCountedMutex() + { + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/simplefileaccessinteraction.cxx b/comphelper/source/misc/simplefileaccessinteraction.cxx new file mode 100644 index 000000000..8cd77af7d --- /dev/null +++ b/comphelper/source/misc/simplefileaccessinteraction.cxx @@ -0,0 +1,120 @@ +/* -*- 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/. + */ + +#include <comphelper/simplefileaccessinteraction.hxx> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/ucb/AuthenticationRequest.hpp> +#include <com/sun/star/ucb/CertificateValidationRequest.hpp> +#include <com/sun/star/ucb/InteractiveIOException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkException.hpp> +#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp> + +namespace comphelper +{ +/// Will handle com::sun::star::ucb::InteractiveIOException and derived classes +const sal_Int32 HANDLE_INTERACTIVEIOEXCEPTION = 0; +/// Will handle com::sun::star::ucb::UnsupportedDataSinkException +const sal_Int32 HANDLE_UNSUPPORTEDDATASINKEXCEPTION = 1; +/// Will handle com::sun::star::ucb::InteractiveNetworkException +const sal_Int32 HANDLE_INTERACTIVENETWORKEXCEPTION = 2; +/// Will handle com::sun::star::ucb::CertificateValidationRequest +const sal_Int32 HANDLE_CERTIFICATEREQUEST = 3; +/// Will handle com::sun::star::ucb::AuthenticationRequest +const sal_Int32 HANDLE_AUTHENTICATIONREQUEST = 4; + +SimpleFileAccessInteraction::SimpleFileAccessInteraction( + const css::uno::Reference<css::task::XInteractionHandler>& xHandler) +{ + std::vector<::ucbhelper::InterceptedInteraction::InterceptedRequest> lInterceptions{ + { //intercept standard IO error exception (local file and WebDAV) + css::uno::Any(css::ucb::InteractiveIOException()), + cppu::UnoType<css::task::XInteractionAbort>::get(), HANDLE_INTERACTIVEIOEXCEPTION }, + { //intercept internal error + css::uno::Any(css::ucb::UnsupportedDataSinkException()), + cppu::UnoType<css::task::XInteractionAbort>::get(), HANDLE_UNSUPPORTEDDATASINKEXCEPTION }, + { + //intercept network error exception (WebDAV ucp provider) + css::uno::Any(css::ucb::InteractiveNetworkException()), + cppu::UnoType<css::task::XInteractionAbort>::get(), + HANDLE_INTERACTIVENETWORKEXCEPTION, + }, + { //intercept certificate validation request (WebDAV ucp provider) + css::uno::Any(css::ucb::CertificateValidationRequest()), + cppu::UnoType<css::task::XInteractionAbort>::get(), HANDLE_CERTIFICATEREQUEST }, + { //intercept authentication request (WebDAV ucp provider) + css::uno::Any(css::ucb::AuthenticationRequest()), + cppu::UnoType<css::task::XInteractionApprove>::get(), HANDLE_AUTHENTICATIONREQUEST } + }; + + setInterceptedHandler(xHandler); + setInterceptions(std::move(lInterceptions)); +} + +SimpleFileAccessInteraction::~SimpleFileAccessInteraction() {} + +ucbhelper::InterceptedInteraction::EInterceptionState SimpleFileAccessInteraction::intercepted( + const ::ucbhelper::InterceptedInteraction::InterceptedRequest& aRequest, + const css::uno::Reference<css::task::XInteractionRequest>& xRequest) +{ + bool bAbort = false; + switch (aRequest.Handle) + { + case HANDLE_UNSUPPORTEDDATASINKEXCEPTION: + case HANDLE_INTERACTIVENETWORKEXCEPTION: + case HANDLE_INTERACTIVEIOEXCEPTION: + { + bAbort = true; + } + break; + + case HANDLE_CERTIFICATEREQUEST: + { + // use default internal handler. + if (m_xInterceptedHandler.is()) + { + m_xInterceptedHandler->handle(xRequest); + return ::ucbhelper::InterceptedInteraction::E_INTERCEPTED; + } + else + bAbort = true; + break; + } + + case HANDLE_AUTHENTICATIONREQUEST: + { + // use default internal handler. + if (m_xInterceptedHandler.is()) + { + m_xInterceptedHandler->handle(xRequest); + return ::ucbhelper::InterceptedInteraction::E_INTERCEPTED; + } + else //simply abort + bAbort = true; + } + break; + } + + // handle interaction by ourself, by not doing + // any selection... + if (bAbort) + { + css::uno::Reference<css::task::XInteractionContinuation> xAbort + = ::ucbhelper::InterceptedInteraction::extractContinuation( + xRequest->getContinuations(), cppu::UnoType<css::task::XInteractionAbort>::get()); + if (!xAbort.is()) + return ::ucbhelper::InterceptedInteraction::E_NO_CONTINUATION_FOUND; + return ::ucbhelper::InterceptedInteraction::E_INTERCEPTED; + } + + return ::ucbhelper::InterceptedInteraction::E_INTERCEPTED; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/solarmutex.cxx b/comphelper/source/misc/solarmutex.cxx new file mode 100644 index 000000000..5d1052327 --- /dev/null +++ b/comphelper/source/misc/solarmutex.cxx @@ -0,0 +1,103 @@ +/* -*- 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 <comphelper/solarmutex.hxx> +#include <osl/thread.hxx> + +#include <assert.h> +#include <cstdlib> + +namespace comphelper { + +namespace { + SolarMutex* g_pSolarMutex = nullptr; +} + +SolarMutex *SolarMutex::get() +{ + return g_pSolarMutex; +} + +SolarMutex::SolarMutex() + : m_nCount( 0 ) + , m_nThreadId( 0 ) + , m_aBeforeReleaseHandler( nullptr ) +{ + assert(!g_pSolarMutex); + g_pSolarMutex = this; +} + +SolarMutex::~SolarMutex() +{ + g_pSolarMutex = nullptr; +} + +void SolarMutex::doAcquire( const sal_uInt32 nLockCount ) +{ + for ( sal_uInt32 n = nLockCount; n ; --n ) + m_aMutex.acquire(); + m_nThreadId = osl::Thread::getCurrentIdentifier(); + m_nCount += nLockCount; +} + +sal_uInt32 SolarMutex::doRelease( bool bUnlockAll ) +{ + if ( !IsCurrentThread() ) + std::abort(); + if ( m_nCount == 0 ) + std::abort(); + + const sal_uInt32 nCount = bUnlockAll ? m_nCount : 1; + m_nCount -= nCount; + + if ( 0 == m_nCount ) + { + if ( m_aBeforeReleaseHandler ) + m_aBeforeReleaseHandler(); + m_nThreadId = 0; + } + + for ( sal_uInt32 n = nCount ; n ; --n ) + m_aMutex.release(); + + return nCount; +} + +bool SolarMutex::IsCurrentThread() const +{ + return m_nThreadId == osl::Thread::getCurrentIdentifier(); +} + +bool SolarMutex::tryToAcquire() +{ + if ( m_aMutex.tryToAcquire() ) + { + m_nThreadId = osl::Thread::getCurrentIdentifier(); + m_nCount++; + return true; + } + else + return false; +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/stillreadwriteinteraction.cxx b/comphelper/source/misc/stillreadwriteinteraction.cxx new file mode 100644 index 000000000..88bc25bc4 --- /dev/null +++ b/comphelper/source/misc/stillreadwriteinteraction.cxx @@ -0,0 +1,156 @@ +/* -*- 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/stillreadwriteinteraction.hxx> + +#include <com/sun/star/ucb/InteractiveIOException.hpp> + +#include <com/sun/star/task/XInteractionAbort.hpp> + +#include <com/sun/star/task/XInteractionApprove.hpp> + +#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp> + +#include <com/sun/star/ucb/AuthenticationRequest.hpp> + +#include <com/sun/star/ucb/CertificateValidationRequest.hpp> +#include <utility> + +namespace comphelper{ + +StillReadWriteInteraction::StillReadWriteInteraction(const css::uno::Reference< css::task::XInteractionHandler >& xHandler, + css::uno::Reference< css::task::XInteractionHandler > xAuxiliaryHandler) + : m_bUsed (false) + , m_bHandledByMySelf (false) + , m_xAuxiliaryHandler(std::move(xAuxiliaryHandler)) +{ + std::vector< ::ucbhelper::InterceptedInteraction::InterceptedRequest > lInterceptions; + lInterceptions.reserve(4); + ::ucbhelper::InterceptedInteraction::InterceptedRequest aInterceptedRequest; + + aInterceptedRequest.Handle = HANDLE_INTERACTIVEIOEXCEPTION; + aInterceptedRequest.Request <<= css::ucb::InteractiveIOException(); + aInterceptedRequest.Continuation = cppu::UnoType<css::task::XInteractionAbort>::get(); + lInterceptions.push_back(aInterceptedRequest); + + aInterceptedRequest.Handle = HANDLE_UNSUPPORTEDDATASINKEXCEPTION; + aInterceptedRequest.Request <<= css::ucb::UnsupportedDataSinkException(); + aInterceptedRequest.Continuation = cppu::UnoType<css::task::XInteractionAbort>::get(); + lInterceptions.push_back(aInterceptedRequest); + + aInterceptedRequest.Handle = HANDLE_AUTHENTICATIONREQUESTEXCEPTION; + aInterceptedRequest.Request <<= css::ucb::AuthenticationRequest(); + aInterceptedRequest.Continuation = cppu::UnoType<css::task::XInteractionApprove>::get(); + lInterceptions.push_back(aInterceptedRequest); + + aInterceptedRequest.Handle = HANDLE_CERTIFICATEVALIDATIONREQUESTEXCEPTION; + aInterceptedRequest.Request <<= css::ucb::CertificateValidationRequest(); + aInterceptedRequest.Continuation = cppu::UnoType<css::task::XInteractionApprove>::get(); + lInterceptions.push_back(aInterceptedRequest); + + setInterceptedHandler(xHandler); + setInterceptions(std::move(lInterceptions)); +} + +void StillReadWriteInteraction::resetInterceptions() +{ + setInterceptions(std::vector< ::ucbhelper::InterceptedInteraction::InterceptedRequest >()); +} + +void StillReadWriteInteraction::resetErrorStates() +{ + m_bUsed = false; + m_bHandledByMySelf = false; +} + + +ucbhelper::InterceptedInteraction::EInterceptionState StillReadWriteInteraction::intercepted(const ::ucbhelper::InterceptedInteraction::InterceptedRequest& aRequest, + const css::uno::Reference< css::task::XInteractionRequest >& xRequest) +{ + // we are used! + m_bUsed = true; + + // check if it's a real interception - might some parameters are not the right ones ... + bool bAbort = false; + switch(aRequest.Handle) + { + case HANDLE_INTERACTIVEIOEXCEPTION: + { + css::ucb::InteractiveIOException exIO; + xRequest->getRequest() >>= exIO; + bAbort = ( + (exIO.Code == css::ucb::IOErrorCode_ACCESS_DENIED ) + || (exIO.Code == css::ucb::IOErrorCode_LOCKING_VIOLATION ) + || (exIO.Code == css::ucb::IOErrorCode_NOT_EXISTING ) + // At least on Linux, a request to open some fuse-mounted file O_RDWR may fail with + // EOPNOTSUPP (mapped to osl_File_E_NOSYS to IOErrorCode_NOT_SUPPORTED) when opening + // it O_RDONLY would succeed: + || (exIO.Code == css::ucb::IOErrorCode_NOT_SUPPORTED ) +#ifdef MACOSX + // this is a workaround for MAC, on this platform if the file is locked + // the returned error code looks to be wrong + || (exIO.Code == css::ucb::IOErrorCode_GENERAL ) +#endif + ); + } + break; + + case HANDLE_UNSUPPORTEDDATASINKEXCEPTION: + { + bAbort = true; + } + break; + case HANDLE_CERTIFICATEVALIDATIONREQUESTEXCEPTION: + case HANDLE_AUTHENTICATIONREQUESTEXCEPTION: + { +//use internal auxiliary handler and return + if (m_xAuxiliaryHandler.is()) + { + m_xAuxiliaryHandler->handle(xRequest); + return ::ucbhelper::InterceptedInteraction::E_INTERCEPTED; + } + else //simply abort + bAbort = true; + } + break; + } + + // handle interaction by ourself + if (bAbort) + { + m_bHandledByMySelf = true; + css::uno::Reference< css::task::XInteractionContinuation > xAbort = ::ucbhelper::InterceptedInteraction::extractContinuation( + xRequest->getContinuations(), + cppu::UnoType<css::task::XInteractionAbort>::get() ); + if (!xAbort.is()) + return ::ucbhelper::InterceptedInteraction::E_NO_CONTINUATION_FOUND; + xAbort->select(); + return ::ucbhelper::InterceptedInteraction::E_INTERCEPTED; + } + + // Otherwise use internal handler. + if (m_xInterceptedHandler.is()) + { + m_xInterceptedHandler->handle(xRequest); + } + return ::ucbhelper::InterceptedInteraction::E_INTERCEPTED; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/storagehelper.cxx b/comphelper/source/misc/storagehelper.cxx new file mode 100644 index 000000000..5f8527bcf --- /dev/null +++ b/comphelper/source/misc/storagehelper.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 <config_gpgme.h> + +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/XEncryptionProtectedStorage.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/embed/StorageFactory.hpp> +#include <com/sun/star/embed/FileSystemStorageFactory.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/IllegalTypeException.hpp> +#include <com/sun/star/xml/crypto/NSSInitializer.hpp> +#include <com/sun/star/xml/crypto/XDigestContext.hpp> +#include <com/sun/star/xml/crypto/DigestID.hpp> +#include <com/sun/star/security/DocumentDigitalSignatures.hpp> +#include <com/sun/star/security/XCertificate.hpp> + +#include <vector> + +#include <rtl/digest.h> +#include <rtl/random.h> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include <ucbhelper/content.hxx> + +#include <comphelper/bytereader.hxx> +#include <comphelper/fileformat.h> +#include <comphelper/hash.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/documentconstants.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <o3tl/string_view.hxx> + +#if HAVE_FEATURE_GPGME +# include <context.h> +# include <encryptionresult.h> +# include <key.h> +# include <data.h> +#endif + +using namespace ::com::sun::star; + +namespace comphelper { + + +uno::Reference< lang::XSingleServiceFactory > OStorageHelper::GetStorageFactory( + const uno::Reference< uno::XComponentContext >& rxContext ) +{ + uno::Reference< uno::XComponentContext> xContext = rxContext.is() ? rxContext : ::comphelper::getProcessComponentContext(); + + return embed::StorageFactory::create( xContext ); +} + + +uno::Reference< lang::XSingleServiceFactory > OStorageHelper::GetFileSystemStorageFactory( + const uno::Reference< uno::XComponentContext >& rxContext ) +{ + return embed::FileSystemStorageFactory::create(rxContext); +} + + +uno::Reference< embed::XStorage > OStorageHelper::GetTemporaryStorage( + const uno::Reference< uno::XComponentContext >& rxContext ) +{ + uno::Reference< embed::XStorage > xTempStorage( GetStorageFactory( rxContext )->createInstance(), + uno::UNO_QUERY_THROW ); + return xTempStorage; +} + + +uno::Reference< embed::XStorage > OStorageHelper::GetStorageFromURL( + const OUString& aURL, + sal_Int32 nStorageMode, + const uno::Reference< uno::XComponentContext >& rxContext ) +{ + uno::Sequence< uno::Any > aArgs{ uno::Any(aURL), uno::Any(nStorageMode) }; + uno::Reference< embed::XStorage > xTempStorage( GetStorageFactory( rxContext )->createInstanceWithArguments( aArgs ), + uno::UNO_QUERY_THROW ); + return xTempStorage; +} + + +uno::Reference< embed::XStorage > OStorageHelper::GetStorageFromURL2( + const OUString& aURL, + sal_Int32 nStorageMode, + const uno::Reference< uno::XComponentContext >& rxContext ) +{ + uno::Sequence< uno::Any > aArgs{ uno::Any(aURL), uno::Any(nStorageMode) }; + + uno::Reference< lang::XSingleServiceFactory > xFact; + css::uno::Any anyEx; + try { + ::ucbhelper::Content aCntnt( aURL, + uno::Reference< css::ucb::XCommandEnvironment > (), + getProcessComponentContext() ); + if (aCntnt.isDocument()) { + xFact = GetStorageFactory( rxContext ); + } else { + xFact = GetFileSystemStorageFactory( rxContext ); + } + } catch (uno::Exception &) + { + anyEx = cppu::getCaughtException(); + } + + if (!xFact.is()) + { + if (anyEx.hasValue()) + throw css::lang::WrappedTargetRuntimeException( "", nullptr, anyEx ); + else + throw uno::RuntimeException(); + } + + uno::Reference< embed::XStorage > xTempStorage( + xFact->createInstanceWithArguments( aArgs ), uno::UNO_QUERY_THROW ); + return xTempStorage; +} + + +uno::Reference< embed::XStorage > OStorageHelper::GetStorageFromInputStream( + const uno::Reference < io::XInputStream >& xStream, + const uno::Reference< uno::XComponentContext >& rxContext ) +{ + uno::Sequence< uno::Any > aArgs{ uno::Any(xStream), uno::Any(embed::ElementModes::READ) }; + uno::Reference< embed::XStorage > xTempStorage( GetStorageFactory( rxContext )->createInstanceWithArguments( aArgs ), + uno::UNO_QUERY_THROW ); + return xTempStorage; +} + + +uno::Reference< embed::XStorage > OStorageHelper::GetStorageFromStream( + const uno::Reference < io::XStream >& xStream, + sal_Int32 nStorageMode, + const uno::Reference< uno::XComponentContext >& rxContext ) +{ + uno::Sequence< uno::Any > aArgs{ uno::Any(xStream), uno::Any(nStorageMode) }; + uno::Reference< embed::XStorage > xTempStorage( GetStorageFactory( rxContext )->createInstanceWithArguments( aArgs ), + uno::UNO_QUERY_THROW ); + return xTempStorage; +} + + +void OStorageHelper::CopyInputToOutput( + const uno::Reference< io::XInputStream >& xInput, + const uno::Reference< io::XOutputStream >& xOutput ) +{ + static const sal_Int32 nConstBufferSize = 32000; + + uno::Reference< css::lang::XUnoTunnel > xInputTunnel( xInput, uno::UNO_QUERY ); + comphelper::ByteReader* pByteReader = nullptr; + comphelper::ByteWriter* pByteWriter = nullptr; + if (xInputTunnel) + pByteReader = reinterpret_cast< comphelper::ByteReader* >( xInputTunnel->getSomething( comphelper::ByteReader::getUnoTunnelId() ) ); + if (pByteReader) + { + uno::Reference< css::lang::XUnoTunnel > xOutputTunnel( xOutput, uno::UNO_QUERY ); + if (xOutputTunnel) + pByteWriter = reinterpret_cast< comphelper::ByteWriter* >( xOutputTunnel->getSomething( comphelper::ByteWriter::getUnoTunnelId() ) ); + } + + if (pByteWriter) + { + sal_Int32 nRead; + sal_Int8 aTempBuf[ nConstBufferSize ]; + do + { + nRead = pByteReader->readSomeBytes ( aTempBuf, nConstBufferSize ); + pByteWriter->writeSomeBytes ( aTempBuf, nRead ); + } + while ( nRead == nConstBufferSize ); + } + else + { + sal_Int32 nRead; + uno::Sequence < sal_Int8 > aSequence ( nConstBufferSize ); + + do + { + nRead = xInput->readBytes ( aSequence, nConstBufferSize ); + if ( nRead < nConstBufferSize ) + { + uno::Sequence < sal_Int8 > aTempBuf ( aSequence.getConstArray(), nRead ); + xOutput->writeBytes ( aTempBuf ); + } + else + xOutput->writeBytes ( aSequence ); + } + while ( nRead == nConstBufferSize ); + } +} + + +uno::Reference< io::XInputStream > OStorageHelper::GetInputStreamFromURL( + const OUString& aURL, + const uno::Reference< uno::XComponentContext >& context ) +{ + uno::Reference< io::XInputStream > xInputStream = ucb::SimpleFileAccess::create(context)->openFileRead( aURL ); + if ( !xInputStream.is() ) + throw uno::RuntimeException(); + + return xInputStream; +} + + +void OStorageHelper::SetCommonStorageEncryptionData( + const uno::Reference< embed::XStorage >& xStorage, + const uno::Sequence< beans::NamedValue >& aEncryptionData ) +{ + uno::Reference< embed::XEncryptionProtectedStorage > xEncrSet( xStorage, uno::UNO_QUERY ); + if ( !xEncrSet.is() ) + throw io::IOException("no XEncryptionProtectedStorage"); // TODO + + if ( aEncryptionData.getLength() == 2 && + aEncryptionData[0].Name == "GpgInfos" && + aEncryptionData[1].Name == "EncryptionKey" ) + { + xEncrSet->setGpgProperties( + aEncryptionData[0].Value.get< uno::Sequence< uno::Sequence< beans::NamedValue > > >() ); + xEncrSet->setEncryptionData( + aEncryptionData[1].Value.get< uno::Sequence< beans::NamedValue > >() ); + } + else + xEncrSet->setEncryptionData( aEncryptionData ); +} + + +sal_Int32 OStorageHelper::GetXStorageFormat( + const uno::Reference< embed::XStorage >& xStorage ) +{ + uno::Reference< beans::XPropertySet > xStorProps( xStorage, uno::UNO_QUERY_THROW ); + + OUString aMediaType; + xStorProps->getPropertyValue("MediaType") >>= aMediaType; + + sal_Int32 nResult = 0; + + // TODO/LATER: the filter configuration could be used to detect it later, or better a special service + if ( + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_VND_SUN_XML_WRITER_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_VND_SUN_XML_WRITER_WEB_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_VND_SUN_XML_WRITER_GLOBAL_ASCII) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_VND_SUN_XML_DRAW_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_VND_SUN_XML_IMPRESS_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_VND_SUN_XML_CALC_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_VND_SUN_XML_CHART_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_VND_SUN_XML_MATH_ASCII ) + ) + { + nResult = SOFFICE_FILEFORMAT_60; + } + else if ( + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_TEXT_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_TEXT_WEB_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_TEXT_GLOBAL_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_DRAWING_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_PRESENTATION_ASCII) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_SPREADSHEET_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_CHART_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_FORMULA_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_DATABASE_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_REPORT_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_REPORT_CHART_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_TEXT_TEMPLATE_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_TEXT_GLOBAL_TEMPLATE_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_DRAWING_TEMPLATE_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_PRESENTATION_TEMPLATE_ASCII) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_SPREADSHEET_TEMPLATE_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_CHART_TEMPLATE_ASCII ) || + aMediaType.equalsIgnoreAsciiCase(MIMETYPE_OASIS_OPENDOCUMENT_FORMULA_TEMPLATE_ASCII ) + ) + { + nResult = SOFFICE_FILEFORMAT_8; + } + else + { + // the mediatype is not known + OUString aMsg = __func__ + + OUString::Concat(u":") + + OUString::number(__LINE__) + + ": unknown media type '" + + aMediaType + + "'"; + throw beans::IllegalTypeException(aMsg); + } + + return nResult; +} + + +uno::Reference< embed::XStorage > OStorageHelper::GetStorageOfFormatFromURL( + const OUString& aFormat, + const OUString& aURL, + sal_Int32 nStorageMode, + const uno::Reference< uno::XComponentContext >& rxContext ) +{ + uno::Sequence< beans::PropertyValue > aProps{ comphelper::makePropertyValue("StorageFormat", + aFormat) }; + + uno::Sequence< uno::Any > aArgs{ uno::Any(aURL), uno::Any(nStorageMode), uno::Any(aProps) }; + uno::Reference< embed::XStorage > xTempStorage( GetStorageFactory( rxContext )->createInstanceWithArguments( aArgs ), + uno::UNO_QUERY_THROW ); + return xTempStorage; +} + + +uno::Reference< embed::XStorage > OStorageHelper::GetStorageOfFormatFromInputStream( + const OUString& aFormat, + const uno::Reference < io::XInputStream >& xStream, + const uno::Reference< uno::XComponentContext >& rxContext, + bool bRepairStorage ) +{ + uno::Sequence< beans::PropertyValue > aProps( bRepairStorage ? 2 : 1 ); + auto pProps = aProps.getArray(); + pProps[0].Name = "StorageFormat"; + pProps[0].Value <<= aFormat; + if ( bRepairStorage ) + { + pProps[1].Name = "RepairPackage"; + pProps[1].Value <<= bRepairStorage; + } + + uno::Sequence< uno::Any > aArgs{ uno::Any(xStream), uno::Any(embed::ElementModes::READ), uno::Any(aProps) }; + uno::Reference< embed::XStorage > xTempStorage( GetStorageFactory( rxContext )->createInstanceWithArguments( aArgs ), + uno::UNO_QUERY_THROW ); + return xTempStorage; +} + + +uno::Reference< embed::XStorage > OStorageHelper::GetStorageOfFormatFromStream( + const OUString& aFormat, + const uno::Reference < io::XStream >& xStream, + sal_Int32 nStorageMode, + const uno::Reference< uno::XComponentContext >& rxContext, + bool bRepairStorage ) +{ + uno::Sequence< beans::PropertyValue > aProps( bRepairStorage ? 2 : 1 ); + auto pProps = aProps.getArray(); + pProps[0].Name = "StorageFormat"; + pProps[0].Value <<= aFormat; + if ( bRepairStorage ) + { + pProps[1].Name = "RepairPackage"; + pProps[1].Value <<= bRepairStorage; + } + + uno::Sequence< uno::Any > aArgs{ uno::Any(xStream), uno::Any(nStorageMode), uno::Any(aProps) }; + uno::Reference< embed::XStorage > xTempStorage( GetStorageFactory( rxContext )->createInstanceWithArguments( aArgs ), + uno::UNO_QUERY_THROW ); + return xTempStorage; +} + + +uno::Sequence< beans::NamedValue > OStorageHelper::CreatePackageEncryptionData( std::u16string_view aPassword ) +{ + // TODO/LATER: Should not the method be part of DocPasswordHelper? + uno::Sequence< beans::NamedValue > aEncryptionData; + if ( !aPassword.empty() ) + { + sal_Int32 nSha1Ind = 0; + // generate SHA256 start key + try + { + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + + uno::Reference< css::xml::crypto::XNSSInitializer > xDigestContextSupplier = css::xml::crypto::NSSInitializer::create(xContext); + uno::Reference< css::xml::crypto::XDigestContext > xDigestContext( xDigestContextSupplier->getDigestContext( css::xml::crypto::DigestID::SHA256, uno::Sequence< beans::NamedValue >() ), uno::UNO_SET_THROW ); + + OString aUTF8Password( OUStringToOString( aPassword, RTL_TEXTENCODING_UTF8 ) ); + xDigestContext->updateDigest( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( aUTF8Password.getStr() ), aUTF8Password.getLength() ) ); + uno::Sequence< sal_Int8 > aDigest = xDigestContext->finalizeDigestAndDispose(); + + ++nSha1Ind; + aEncryptionData = { { PACKAGE_ENCRYPTIONDATA_SHA256UTF8, uno::Any(aDigest) } }; + } + catch ( uno::Exception& ) + { + OSL_ENSURE( false, "Can not create SHA256 digest!" ); + } + + // MS_1252 encoding was used for SO60 document format password encoding, + // this encoding supports only a minor subset of nonascii characters, + // but for compatibility reasons it has to be used for old document formats + aEncryptionData.realloc( nSha1Ind + 3 ); + auto pEncryptionData = aEncryptionData.getArray(); + // these are StarOffice not-quite-SHA1 + pEncryptionData[nSha1Ind].Name = PACKAGE_ENCRYPTIONDATA_SHA1UTF8; + pEncryptionData[nSha1Ind + 1].Name = PACKAGE_ENCRYPTIONDATA_SHA1MS1252; + + rtl_TextEncoding const pEncoding[2] = { RTL_TEXTENCODING_UTF8, RTL_TEXTENCODING_MS_1252 }; + + for ( sal_Int32 nInd = 0; nInd < 2; nInd++ ) + { + OString aByteStrPass = OUStringToOString( aPassword, pEncoding[nInd] ); + + sal_uInt8 pBuffer[RTL_DIGEST_LENGTH_SHA1]; + rtlDigestError nError = rtl_digest_SHA1( aByteStrPass.getStr(), + aByteStrPass.getLength(), + pBuffer, + RTL_DIGEST_LENGTH_SHA1 ); + + if ( nError != rtl_Digest_E_None ) + { + aEncryptionData.realloc( nSha1Ind ); + return aEncryptionData; + } + + // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence + pEncryptionData[nSha1Ind+nInd].Value <<= uno::Sequence< sal_Int8 >( reinterpret_cast<sal_Int8*>(pBuffer), RTL_DIGEST_LENGTH_SHA1 ); + } + + // actual SHA1 + pEncryptionData[nSha1Ind + 2].Name = PACKAGE_ENCRYPTIONDATA_SHA1CORRECT; + OString aByteStrPass = OUStringToOString(aPassword, RTL_TEXTENCODING_UTF8); + std::vector<unsigned char> const sha1(::comphelper::Hash::calculateHash( + reinterpret_cast<unsigned char const*>(aByteStrPass.getStr()), aByteStrPass.getLength(), + ::comphelper::HashType::SHA1)); + pEncryptionData[nSha1Ind + 2].Value <<= uno::Sequence<sal_Int8>( + reinterpret_cast<sal_Int8 const*>(sha1.data()), sha1.size()); + } + + return aEncryptionData; +} + +uno::Sequence< beans::NamedValue > OStorageHelper::CreateGpgPackageEncryptionData() +{ +#if HAVE_FEATURE_GPGME + // generate session key + // -------------------- + + rtlRandomPool aRandomPool = rtl_random_createPool(); + + // get 32 random chars out of it + uno::Sequence < sal_Int8 > aVector(32); + rtl_random_getBytes( aRandomPool, aVector.getArray(), aVector.getLength() ); + + rtl_random_destroyPool(aRandomPool); + + std::vector< uno::Sequence< beans::NamedValue > > aGpgEncryptions; + + uno::Reference< security::XDocumentDigitalSignatures > xSigner( + // here none of the version-dependent methods are called + security::DocumentDigitalSignatures::createDefault( + comphelper::getProcessComponentContext())); + + // fire up certificate chooser dialog - user can multi-select! + const uno::Sequence< uno::Reference< security::XCertificate > > xSignCertificates= + xSigner->chooseEncryptionCertificate(); + + if (!xSignCertificates.hasElements()) + return uno::Sequence< beans::NamedValue >(); // user cancelled + + // generate one encrypted key entry for each recipient + // --------------------------------------------------- + + std::unique_ptr<GpgME::Context> ctx; + GpgME::Error err = GpgME::checkEngine(GpgME::OpenPGP); + if (err) + throw uno::RuntimeException("The GpgME library failed to initialize for the OpenPGP protocol."); + + ctx.reset( GpgME::Context::createForProtocol(GpgME::OpenPGP) ); + if (ctx == nullptr) + throw uno::RuntimeException("The GpgME library failed to initialize for the OpenPGP protocol."); + ctx->setArmor(false); + + for (const auto & cert : xSignCertificates) + { + uno::Sequence < sal_Int8 > aKeyID; + if (cert.is()) + aKeyID = cert->getSHA1Thumbprint(); + + std::vector<GpgME::Key> keys + { + ctx->key( + reinterpret_cast<const char*>(aKeyID.getConstArray()), + err, false) + }; + + // ctx is setup now, let's encrypt the lot! + GpgME::Data plain( + reinterpret_cast<const char*>(aVector.getConstArray()), + size_t(aVector.getLength()), false); + GpgME::Data cipher; + + GpgME::EncryptionResult crypt_res = ctx->encrypt( + keys, plain, + cipher, GpgME::Context::NoCompress); + + off_t result = cipher.seek(0,SEEK_SET); + (void) result; + assert(result == 0); + int len=0, curr=0; char buf; + while( (curr=cipher.read(&buf, 1)) ) + len += curr; + + if(crypt_res.error() || !len) + throw lang::IllegalArgumentException( + "Not a suitable key, or failed to encrypt.", + css::uno::Reference<css::uno::XInterface>(), -1); + + uno::Sequence < sal_Int8 > aCipherValue(len); + result = cipher.seek(0,SEEK_SET); + assert(result == 0); + if( cipher.read(aCipherValue.getArray(), len) != len ) + throw uno::RuntimeException("The GpgME library failed to read the encrypted value."); + + SAL_INFO("comphelper.crypto", "Generated gpg crypto of length: " << len); + + uno::Sequence< beans::NamedValue > aGpgEncryptionEntry{ + { "KeyId", uno::Any(aKeyID) }, + { "KeyPacket", uno::Any(aKeyID) }, + { "CipherValue", uno::Any(aCipherValue) } + }; + + aGpgEncryptions.push_back(aGpgEncryptionEntry); + } + + uno::Sequence<beans::NamedValue> aEncryptionData + = { { PACKAGE_ENCRYPTIONDATA_SHA256UTF8, uno::Any(aVector) } }; + + uno::Sequence<beans::NamedValue> aContainer + = { { "GpgInfos", uno::Any(comphelper::containerToSequence(aGpgEncryptions)) }, + { "EncryptionKey", uno::Any(aEncryptionData) } }; + + return aContainer; +#else + return uno::Sequence< beans::NamedValue >(); +#endif +} + +bool OStorageHelper::IsValidZipEntryFileName( std::u16string_view aName, bool bSlashAllowed ) +{ + return IsValidZipEntryFileName( aName.data(), aName.size(), bSlashAllowed ); +} + + +bool OStorageHelper::IsValidZipEntryFileName( + const sal_Unicode *pChar, sal_Int32 nLength, bool bSlashAllowed ) +{ + for ( sal_Int32 i = 0; i < nLength; i++ ) + { + switch ( pChar[i] ) + { + case '\\': + case '?': + case '<': + case '>': + case '\"': + case '|': + case ':': + return false; + case '/': + if ( !bSlashAllowed ) + return false; + break; + default: + if ( pChar[i] < 32 || (pChar[i] >= 0xD800 && pChar[i] <= 0xDFFF) ) + return false; + } + } + return true; +} + + +bool OStorageHelper::PathHasSegment( std::u16string_view aPath, std::u16string_view aSegment ) +{ + bool bResult = false; + const size_t nPathLen = aPath.size(); + const size_t nSegLen = aSegment.size(); + + if ( !aSegment.empty() && nPathLen >= nSegLen ) + { + OUString aEndSegment = OUString::Concat("/") + aSegment; + OUString aInternalSegment = aEndSegment + "/"; + + if ( aPath.find( aInternalSegment ) != std::u16string_view::npos ) + bResult = true; + + if ( !bResult && o3tl::starts_with(aPath, aSegment ) ) + { + if ( nPathLen == nSegLen || aPath[nSegLen] == '/' ) + bResult = true; + } + + if ( !bResult && nPathLen > nSegLen && aPath.substr( nPathLen - nSegLen - 1, nSegLen + 1 ) == aEndSegment ) + bResult = true; + } + + return bResult; +} + +class LifecycleProxy::Impl + : public std::vector< uno::Reference< embed::XStorage > > {}; +LifecycleProxy::LifecycleProxy() + : m_xBadness( new Impl ) { } +LifecycleProxy::~LifecycleProxy() { } + +void LifecycleProxy::commitStorages() +{ + std::for_each(m_xBadness->rbegin(), m_xBadness->rend(), // reverse order (outwards) + [](Impl::reference rxItem) { + uno::Reference<embed::XTransactedObject> const xTransaction(rxItem, uno::UNO_QUERY); + if (xTransaction.is()) + { + xTransaction->commit(); + } + }); +} + +static void splitPath( std::vector<OUString> &rElems, std::u16string_view rPath ) +{ + for (sal_Int32 i = 0; i >= 0;) + rElems.push_back( OUString(o3tl::getToken(rPath, 0, '/', i )) ); +} + +static uno::Reference< embed::XStorage > LookupStorageAtPath( + const uno::Reference< embed::XStorage > &xParentStorage, + std::vector<OUString> &rElems, sal_uInt32 nOpenMode, + LifecycleProxy const &rNastiness ) +{ + uno::Reference< embed::XStorage > xStorage( xParentStorage ); + rNastiness.m_xBadness->push_back( xStorage ); + for( size_t i = 0; i < rElems.size() && xStorage.is(); i++ ) + { + xStorage = xStorage->openStorageElement( rElems[i], nOpenMode ); + rNastiness.m_xBadness->push_back( xStorage ); + } + return xStorage; +} + +uno::Reference< embed::XStorage > OStorageHelper::GetStorageAtPath( + const uno::Reference< embed::XStorage > &xStorage, + std::u16string_view rPath, sal_uInt32 nOpenMode, + LifecycleProxy const &rNastiness ) +{ + std::vector<OUString> aElems; + splitPath( aElems, rPath ); + return LookupStorageAtPath( xStorage, aElems, nOpenMode, rNastiness ); +} + +uno::Reference< io::XStream > OStorageHelper::GetStreamAtPath( + const uno::Reference< embed::XStorage > &xParentStorage, + std::u16string_view rPath, sal_uInt32 nOpenMode, + LifecycleProxy const &rNastiness ) +{ + std::vector<OUString> aElems; + splitPath( aElems, rPath ); + OUString aName( aElems.back() ); + aElems.pop_back(); + sal_uInt32 nStorageMode = nOpenMode & ~embed::ElementModes::TRUNCATE; + uno::Reference< embed::XStorage > xStorage( + LookupStorageAtPath( xParentStorage, aElems, nStorageMode, rNastiness ), + uno::UNO_SET_THROW ); + return xStorage->openStreamElement( aName, nOpenMode ); +} + +uno::Reference< io::XStream > OStorageHelper::GetStreamAtPackageURL( + uno::Reference< embed::XStorage > const& xParentStorage, + const OUString& rURL, sal_uInt32 const nOpenMode, + LifecycleProxy const & rNastiness) +{ + OUString path; + if (rURL.startsWithIgnoreAsciiCase("vnd.sun.star.Package:", &path)) + { + return GetStreamAtPath(xParentStorage, path, nOpenMode, rNastiness); + } + return nullptr; +} + +OUString OStorageHelper::GetODFVersionFromStorage(const uno::Reference<embed::XStorage>& xStorage) +{ + OUString aODFVersion; + try + { + uno::Reference<beans::XPropertySet> xPropSet(xStorage, uno::UNO_QUERY_THROW); + xPropSet->getPropertyValue("Version") >>= aODFVersion; + } + catch (uno::Exception&) + { + } + return aODFVersion; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/string.cxx b/comphelper/source/misc/string.cxx new file mode 100644 index 000000000..d40b9136b --- /dev/null +++ b/comphelper/source/misc/string.cxx @@ -0,0 +1,623 @@ +/* -*- 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 <cstddef> +#include <string_view> +#include <utility> +#include <vector> +#include <algorithm> + +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <rtl/character.hxx> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/string.hxx> +#include <rtl/strbuf.hxx> +#include <sal/types.h> + +#include <comphelper/string.hxx> +#include <comphelper/stl_types.hxx> +#include <comphelper/sequence.hxx> + +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/CharType.hpp> +#include <com/sun/star/i18n/Collator.hpp> + + +namespace comphelper::string { + +namespace +{ + template <typename T, typename C> T tmpl_stripStart(const T &rIn, + const C cRemove) + { + if (rIn.empty()) + return rIn; + + typename T::size_type i = 0; + + while (i < rIn.size()) + { + if (rIn[i] != cRemove) + break; + ++i; + } + + return rIn.substr(i); + } + template <typename T, typename C> T tmpl_stripStartString(const T &rIn, + const C cRemove) + { + if (rIn.isEmpty()) + return rIn; + + sal_Int32 i = 0; + + while (i < rIn.getLength()) + { + if (rIn[i] != cRemove) + break; + ++i; + } + + return rIn.copy(i); + } +} + +OString stripStart(const OString& rIn, char c) +{ + return tmpl_stripStartString<OString, char>(rIn, c); +} + +std::string_view stripStart(std::string_view rIn, char c) +{ + return tmpl_stripStart<std::string_view, char>(rIn, c); +} + +OUString stripStart(const OUString& rIn, sal_Unicode c) +{ + return tmpl_stripStartString<OUString, sal_Unicode>(rIn, c); +} + +std::u16string_view stripStart(std::u16string_view rIn, sal_Unicode c) +{ + return tmpl_stripStart<std::u16string_view, sal_Unicode>(rIn, c); +} + +namespace +{ + template <typename T, typename C> T tmpl_stripEnd(const T &rIn, + const C cRemove) + { + if (rIn.empty()) + return rIn; + + typename T::size_type i = rIn.size(); + + while (i > 0) + { + if (rIn[i-1] != cRemove) + break; + --i; + } + + return rIn.substr(0, i); + } + template <typename T, typename C> T tmpl_stripEndString(const T &rIn, + const C cRemove) + { + if (rIn.isEmpty()) + return rIn; + + sal_Int32 i = rIn.getLength(); + + while (i > 0) + { + if (rIn[i-1] != cRemove) + break; + --i; + } + + return rIn.copy(0, i); + } +} + +OString stripEnd(const OString& rIn, char c) +{ + return tmpl_stripEndString<OString, char>(rIn, c); +} + +std::string_view stripEnd(std::string_view rIn, char c) +{ + return tmpl_stripEnd<std::string_view, char>(rIn, c); +} + +OUString stripEnd(const OUString& rIn, sal_Unicode c) +{ + return tmpl_stripEndString<OUString, sal_Unicode>(rIn, c); +} + +std::u16string_view stripEnd(std::u16string_view rIn, sal_Unicode c) +{ + return tmpl_stripEnd<std::u16string_view, sal_Unicode>(rIn, c); +} + +OString strip(const OString& rIn, char c) +{ + auto x = tmpl_stripStartString<OString, char>(rIn, c); + return stripEnd(x, c); +} + +std::string_view strip(std::string_view rIn, char c) +{ + auto x = tmpl_stripStart<std::string_view, char>(rIn, c); + return stripEnd(x, c); +} + +OUString strip(const OUString& rIn, sal_Unicode c) +{ + auto x = tmpl_stripStartString<OUString, sal_Unicode>(rIn, c); + return stripEnd(x, c); +} + +std::u16string_view strip(std::u16string_view rIn, sal_Unicode c) +{ + auto x = tmpl_stripStart<std::u16string_view, sal_Unicode>(rIn, c); + return stripEnd(x, c); +} + +namespace +{ + template <typename T, typename C> sal_Int32 tmpl_getTokenCount( T rIn, + C cTok) + { + // Empty String: TokenCount by Definition is 0 + if (rIn.empty()) + return 0; + + sal_Int32 nTokCount = 1; + for (typename T::size_type i = 0; i < rIn.size(); ++i) + { + if (rIn[i] == cTok) + ++nTokCount; + } + return nTokCount; + } +} + +sal_Int32 getTokenCount(std::string_view rIn, char cTok) +{ + return tmpl_getTokenCount<std::string_view, char>(rIn, cTok); +} + +sal_Int32 getTokenCount(std::u16string_view rIn, sal_Unicode cTok) +{ + return tmpl_getTokenCount<std::u16string_view, sal_Unicode>(rIn, cTok); +} + +static sal_uInt32 decimalStringToNumber( + OUString const & str, sal_Int32 nStart, sal_Int32 nLength ) +{ + sal_uInt32 result = 0; + for( sal_Int32 i = nStart; i < nStart + nLength; ) + { + sal_uInt32 c = str.iterateCodePoints(&i); + sal_uInt32 value = 0; + if( c <= 0x0039) // ASCII decimal digits, most common + value = c - 0x0030; + else if( c >= 0x1D7F6 ) // mathematical monospace digits + value = c - 0x1D7F6; + else if( c >= 0x1D7EC ) // mathematical sans-serif bold digits + value = c - 0x1D7EC; + else if( c >= 0x1D7E2 ) // mathematical sans-serif digits + value = c - 0x1D7E2; + else if( c >= 0x1D7D8 ) // mathematical double-struck digits + value = c - 0x1D7D8; + else if( c >= 0x1D7CE ) // mathematical bold digits + value = c - 0x1D7CE; + else if( c >= 0x11066 ) // brahmi digits + value = c - 0x11066; + else if( c >= 0x104A0 ) // osmanya digits + value = c - 0x104A0; + else if( c >= 0xFF10 ) // fullwidth digits + value = c - 0xFF10; + else if( c >= 0xABF0 ) // meetei mayek digits + value = c - 0xABF0; + else if( c >= 0xAA50 ) // cham digits + value = c - 0xAA50; + else if( c >= 0xA9D0 ) // javanese digits + value = c - 0xA9D0; + else if( c >= 0xA900 ) // kayah li digits + value = c - 0xA900; + else if( c >= 0xA8D0 ) // saurashtra digits + value = c - 0xA8D0; + else if( c >= 0xA620 ) // vai digits + value = c - 0xA620; + else if( c >= 0x1C50 ) // ol chiki digits + value = c - 0x1C50; + else if( c >= 0x1C40 ) // lepcha digits + value = c - 0x1C40; + else if( c >= 0x1BB0 ) // sundanese digits + value = c - 0x1BB0; + else if( c >= 0x1B50 ) // balinese digits + value = c - 0x1B50; + else if( c >= 0x1A90 ) // tai tham tham digits + value = c - 0x1A90; + else if( c >= 0x1A80 ) // tai tham hora digits + value = c - 0x1A80; + else if( c >= 0x19D0 ) // new tai lue digits + value = c - 0x19D0; + else if( c >= 0x1946 ) // limbu digits + value = c - 0x1946; + else if( c >= 0x1810 ) // mongolian digits + value = c - 0x1810; + else if( c >= 0x17E0 ) // khmer digits + value = c - 0x17E0; + else if( c >= 0x1090 ) // myanmar shan digits + value = c - 0x1090; + else if( c >= 0x1040 ) // myanmar digits + value = c - 0x1040; + else if( c >= 0x0F20 ) // tibetan digits + value = c - 0x0F20; + else if( c >= 0x0ED0 ) // lao digits + value = c - 0x0ED0; + else if( c >= 0x0E50 ) // thai digits + value = c - 0x0E50; + else if( c >= 0x0D66 ) // malayalam digits + value = c - 0x0D66; + else if( c >= 0x0CE6 ) // kannada digits + value = c - 0x0CE6; + else if( c >= 0x0C66 ) // telugu digits + value = c - 0x0C66; + else if( c >= 0x0BE6 ) // tamil digits + value = c - 0x0BE6; + else if( c >= 0x0B66 ) // odia digits + value = c - 0x0B66; + else if( c >= 0x0AE6 ) // gujarati digits + value = c - 0x0AE6; + else if( c >= 0x0A66 ) // gurmukhi digits + value = c - 0x0A66; + else if( c >= 0x09E6 ) // bengali digits + value = c - 0x09E6; + else if( c >= 0x0966 ) // devanagari digit + value = c - 0x0966; + else if( c >= 0x07C0 ) // nko digits + value = c - 0x07C0; + else if( c >= 0x06F0 ) // extended arabic-indic digits + value = c - 0x06F0; + else if( c >= 0x0660 ) // arabic-indic digits + value = c - 0x0660; + result = result * 10 + value; + } + return result; +} + +sal_uInt32 decimalStringToNumber( + OUString const & str ) +{ + return decimalStringToNumber(str, 0, str.getLength()); +} + +using namespace ::com::sun::star; + +// convert between sequence of string and comma separated string + +OUString convertCommaSeparated( + uno::Sequence< OUString > const& i_rSeq) +{ + OUStringBuffer buf; + ::comphelper::intersperse( + i_rSeq.begin(), i_rSeq.end(), ::comphelper::OUStringBufferAppender(buf), OUString( ", " )); + return buf.makeStringAndClear(); +} + +std::vector<OUString> + split(std::u16string_view rStr, sal_Unicode cSeparator) +{ + std::vector< OUString > vec; + std::size_t idx = 0; + do + { + std::u16string_view kw = o3tl::getToken(rStr, cSeparator, idx); + kw = o3tl::trim(kw); + if (!kw.empty()) + { + vec.push_back(OUString(kw)); + } + + } while (idx != std::u16string_view::npos); + + return vec; +} + +uno::Sequence< OUString > + convertCommaSeparated( std::u16string_view i_rString ) +{ + std::vector< OUString > vec = split(i_rString, ','); + return comphelper::containerToSequence(vec); +} + +OString join(std::string_view rSeparator, const std::vector<OString>& rSequence) +{ + OStringBuffer aBuffer; + for (size_t i = 0; i < rSequence.size(); ++i) + { + if (i != 0) + aBuffer.append(rSeparator); + aBuffer.append(rSequence[i]); + } + return aBuffer.makeStringAndClear(); +} + +sal_Int32 compareNatural( const OUString & rLHS, const OUString & rRHS, + const uno::Reference< i18n::XCollator > &rCollator, + const uno::Reference< i18n::XBreakIterator > &rBI, + const lang::Locale &rLocale ) +{ + sal_Int32 nRet = 0; + + sal_Int32 nLHSLastNonDigitPos = 0; + sal_Int32 nRHSLastNonDigitPos = 0; + sal_Int32 nLHSFirstDigitPos = 0; + sal_Int32 nRHSFirstDigitPos = 0; + + // Check if the string starts with a digit + sal_Int32 nStartsDigitLHS = rBI->endOfCharBlock(rLHS, nLHSFirstDigitPos, rLocale, i18n::CharType::DECIMAL_DIGIT_NUMBER); + sal_Int32 nStartsDigitRHS = rBI->endOfCharBlock(rRHS, nRHSFirstDigitPos, rLocale, i18n::CharType::DECIMAL_DIGIT_NUMBER); + + if (nStartsDigitLHS > 0 && nStartsDigitRHS > 0) + { + sal_uInt32 nLHS = comphelper::string::decimalStringToNumber(rLHS, 0, nStartsDigitLHS); + sal_uInt32 nRHS = comphelper::string::decimalStringToNumber(rRHS, 0, nStartsDigitRHS); + + if (nLHS != nRHS) + return nLHS < nRHS ? -1 : 1; + nLHSLastNonDigitPos = nStartsDigitLHS; + nRHSLastNonDigitPos = nStartsDigitRHS; + } + else if (nStartsDigitLHS > 0) + return -1; + else if (nStartsDigitRHS > 0) + return 1; + + while (nLHSFirstDigitPos < rLHS.getLength() || nRHSFirstDigitPos < rRHS.getLength()) + { + sal_Int32 nLHSChunkLen; + sal_Int32 nRHSChunkLen; + + //Compare non digit block as normal strings + nLHSFirstDigitPos = rBI->nextCharBlock(rLHS, nLHSLastNonDigitPos, rLocale, i18n::CharType::DECIMAL_DIGIT_NUMBER); + nRHSFirstDigitPos = rBI->nextCharBlock(rRHS, nRHSLastNonDigitPos, rLocale, i18n::CharType::DECIMAL_DIGIT_NUMBER); + + if (nLHSFirstDigitPos == -1) + nLHSFirstDigitPos = rLHS.getLength(); + + if (nRHSFirstDigitPos == -1) + nRHSFirstDigitPos = rRHS.getLength(); + + nLHSChunkLen = nLHSFirstDigitPos - nLHSLastNonDigitPos; + nRHSChunkLen = nRHSFirstDigitPos - nRHSLastNonDigitPos; + + nRet = rCollator->compareSubstring(rLHS, nLHSLastNonDigitPos, nLHSChunkLen, rRHS, nRHSLastNonDigitPos, nRHSChunkLen); + if (nRet != 0) + break; + + //Compare digit block as one number vs another + nLHSLastNonDigitPos = rBI->endOfCharBlock(rLHS, nLHSFirstDigitPos, rLocale, i18n::CharType::DECIMAL_DIGIT_NUMBER); + nRHSLastNonDigitPos = rBI->endOfCharBlock(rRHS, nRHSFirstDigitPos, rLocale, i18n::CharType::DECIMAL_DIGIT_NUMBER); + if (nLHSLastNonDigitPos == -1) + nLHSLastNonDigitPos = rLHS.getLength(); + if (nRHSLastNonDigitPos == -1) + nRHSLastNonDigitPos = rRHS.getLength(); + nLHSChunkLen = nLHSLastNonDigitPos - nLHSFirstDigitPos; + nRHSChunkLen = nRHSLastNonDigitPos - nRHSFirstDigitPos; + + //To-Do: Possibly scale down those unicode codepoints that relate to + //numbers outside of the normal 0-9 range, e.g. see GetLocalizedChar in + //vcl + + sal_uInt32 nLHS = comphelper::string::decimalStringToNumber(rLHS, nLHSFirstDigitPos, nLHSChunkLen); + sal_uInt32 nRHS = comphelper::string::decimalStringToNumber(rRHS, nRHSFirstDigitPos, nRHSChunkLen); + + if (nLHS != nRHS) + { + nRet = (nLHS < nRHS) ? -1 : 1; + break; + } + } + + return nRet; +} + +NaturalStringSorter::NaturalStringSorter( + const uno::Reference< uno::XComponentContext > &rContext, + lang::Locale aLocale) : m_aLocale(std::move(aLocale)) +{ + m_xCollator = i18n::Collator::create( rContext ); + m_xCollator->loadDefaultCollator(m_aLocale, 0); + m_xBI = i18n::BreakIterator::create( rContext ); +} + +bool isdigitAsciiString(std::string_view rString) +{ + return std::all_of( + rString.data(), rString.data() + rString.size(), + [](unsigned char c){ return rtl::isAsciiDigit(c); }); +} + +bool isdigitAsciiString(std::u16string_view rString) +{ + return std::all_of( + rString.data(), rString.data() + rString.size(), + [](sal_Unicode c){ return rtl::isAsciiDigit(c); }); +} + +namespace +{ + template <typename T, typename I, typename O> T tmpl_reverseString(I rIn) + { + if (rIn.empty()) + return T(); + + typename I::size_type i = rIn.size(); + O sBuf(static_cast<sal_Int32>(i)); + while (i) + sBuf.append(rIn[--i]); + return sBuf.makeStringAndClear(); + } +} + +OUString reverseString(std::u16string_view rStr) +{ + return tmpl_reverseString<OUString, std::u16string_view, OUStringBuffer>(rStr); +} + +OString reverseString(std::string_view rStr) +{ + return tmpl_reverseString<OString, std::string_view, OStringBuffer>(rStr); +} + +OUString reverseCodePoints(OUString const & str) { + auto const len = str.getLength(); + OUStringBuffer buf(len); + for (auto i = len; i != 0;) { + buf.appendUtf32(str.iterateCodePoints(&i, -1)); + } + return buf.makeStringAndClear(); +} + +sal_Int32 indexOfAny(std::u16string_view rIn, + sal_Unicode const*const pChars, sal_Int32 const nPos) +{ + for (std::u16string_view::size_type i = nPos; i < rIn.size(); ++i) + { + sal_Unicode const c = rIn[i]; + for (sal_Unicode const* pChar = pChars; *pChar; ++pChar) + { + if (c == *pChar) + { + return i; + } + } + } + return -1; +} + +OUString removeAny(std::u16string_view rIn, + sal_Unicode const*const pChars) +{ + OUStringBuffer buf; + bool isFound(false); + for (std::u16string_view::size_type i = 0; i < rIn.size(); ++i) + { + sal_Unicode const c = rIn[i]; + bool removeC(false); + for (sal_Unicode const* pChar = pChars; *pChar; ++pChar) + { + if (c == *pChar) + { + removeC = true; + break; + } + } + if (removeC) + { + if (!isFound) + { + if (i > 0) + { + buf.append(rIn.substr(0, i)); + } + isFound = true; + } + } + else if (isFound) + { + buf.append(c); + } + } + return isFound ? buf.makeStringAndClear() : OUString(rIn); +} + +OUString setToken(const OUString& rIn, sal_Int32 nToken, sal_Unicode cTok, + std::u16string_view rNewToken) +{ + sal_Int32 nLen = rIn.getLength(); + sal_Int32 nTok = 0; + sal_Int32 nFirstChar = 0; + sal_Int32 i = 0; + + // Determine token position and length + while ( i < nLen ) + { + // Increase token count if match + if (rIn[i] == cTok) + { + ++nTok; + + if (nTok == nToken) + nFirstChar = i+1; + else if (nTok > nToken) + break; + } + + ++i; + } + + if (nTok >= nToken) + return rIn.replaceAt(nFirstChar, i-nFirstChar, rNewToken); + return rIn; +} + +/** Similar to OUString::replaceAt, but for an OUStringBuffer. + + Replace n = count characters + from position index in this string with newStr. + */ +void replaceAt(OUStringBuffer& rIn, sal_Int32 nIndex, sal_Int32 nCount, std::u16string_view newStr ) +{ + assert(nIndex >= 0 && nIndex <= rIn.getLength()); + assert(nCount >= 0); + assert(nCount <= rIn.getLength() - nIndex); + + /* Append? */ + const sal_Int32 nOldLength = rIn.getLength(); + if ( nIndex == nOldLength ) + { + rIn.append(newStr); + return; + } + + sal_Int32 nNewLength = nOldLength + newStr.size() - nCount; + if (newStr.size() > o3tl::make_unsigned(nCount)) + rIn.ensureCapacity(nOldLength + newStr.size() - nCount); + + sal_Unicode* pStr = const_cast<sal_Unicode*>(rIn.getStr()); + memmove(pStr + nIndex + newStr.size(), pStr + nIndex + nCount, nOldLength - nIndex + nCount); + memcpy(pStr + nIndex, newStr.data(), newStr.size()); + + rIn.setLength(nNewLength); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/synchronousdispatch.cxx b/comphelper/source/misc/synchronousdispatch.cxx new file mode 100644 index 000000000..1602c8963 --- /dev/null +++ b/comphelper/source/misc/synchronousdispatch.cxx @@ -0,0 +1,81 @@ +/* -*- 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/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XSynchronousDispatch.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +#include <comphelper/synchronousdispatch.hxx> +#include <comphelper/processfactory.hxx> +#include <sal/log.hxx> + +namespace comphelper +{ + + +using namespace ::com::sun::star; + +uno::Reference< lang::XComponent > SynchronousDispatch::dispatch( + const uno::Reference< uno::XInterface > &xStartPoint, + const OUString &sURL, + const OUString &sTarget, + const uno::Sequence< beans::PropertyValue > &lArguments ) +{ + util::URL aURL; + aURL.Complete = sURL; + uno::Reference < util::XURLTransformer > xTrans = util::URLTransformer::create( ::comphelper::getProcessComponentContext() ); + xTrans->parseStrict( aURL ); + + uno::Reference < frame::XDispatch > xDispatcher; + uno::Reference < frame::XDispatchProvider > xProvider( xStartPoint, uno::UNO_QUERY ); + + if ( xProvider.is() ) + xDispatcher = xProvider->queryDispatch( aURL, sTarget, 0 ); + + uno::Reference < lang::XComponent > aComponent; + + if ( xDispatcher.is() ) + { + try + { + uno::Any aRet; + uno::Reference < frame::XSynchronousDispatch > xSyncDisp( xDispatcher, uno::UNO_QUERY_THROW ); + + aRet = xSyncDisp->dispatchWithReturnValue( aURL, lArguments ); + + aRet >>= aComponent; + } + catch ( uno::Exception& ) + { + // can't use TOOLS_WARN_EXCEPTION, as comphelper is used by libtl! + SAL_WARN("comphelper", "SynchronousDispatch::dispatch(): error while dispatching '" + << sURL << "' for '" << sTarget << "'!"); + } + } + + return aComponent; +} + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/syntaxhighlight.cxx b/comphelper/source/misc/syntaxhighlight.cxx new file mode 100644 index 000000000..3ce8086e6 --- /dev/null +++ b/comphelper/source/misc/syntaxhighlight.cxx @@ -0,0 +1,740 @@ +/* -*- 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 <cassert> + +#include <rtl/character.hxx> +#include <unicode/uchar.h> +#include <comphelper/syntaxhighlight.hxx> +#include <o3tl/typed_flags_set.hxx> + +namespace { + +// Flags for character properties +enum class CharFlags { + StartIdentifier = 0x0001, + InIdentifier = 0x0002, + StartNumber = 0x0004, + InNumber = 0x0008, + InHexNumber = 0x0010, + InOctNumber = 0x0020, + StartString = 0x0040, + Operator = 0x0080, + Space = 0x0100, + EOL = 0x0200 +}; + +} + +namespace o3tl { + template<> struct typed_flags<CharFlags> : is_typed_flags<CharFlags, 0x03ff> {}; +} + +// ########################################################################## +// ATTENTION: all these words need to be in lower case +// ########################################################################## +static const char* strListBasicKeyWords[] = { + "access", + "alias", + "and", + "any", + "append", + "as", + "attribute", + "base", + "binary", + "boolean", + "byref", + "byte", + "byval", + "call", + "case", + "cdecl", + "classmodule", + "close", + "compare", + "compatible", + "const", + "currency", + "date", + "declare", + "defbool", + "defcur", + "defdate", + "defdbl", + "deferr", + "defint", + "deflng", + "defobj", + "defsng", + "defstr", + "defvar", + "dim", + "do", + "doevents", + "double", + "each", + "else", + "elseif", + "end", + "end enum", + "end function", + "end if", + "end property", + "end select", + "end sub", + "end type", + "endif", + "enum", + "eqv", + "erase", + "error", + "exit", + "explicit", + "for", + "function", + "get", + "global", + "gosub", + "goto", + "if", + "imp", + "implements", + "in", + "input", + "integer", + "is", + "let", + "lib", + "like", + "line", + "line input", + "local", + "lock", + "long", + "loop", + "lprint", + "lset", + "mod", + "name", + "new", + "next", + "not", + "object", + "on", + "open", + "option", + "optional", + "or", + "output", + "paramarray", + "preserve", + "print", + "private", + "property", + "public", + "random", + "read", + "redim", + "rem", + "resume", + "return", + "rset", + "select", + "set", + "shared", + "single", + "static", + "step", + "stop", + "string", + "sub", + "system", + "text", + "then", + "to", + "type", + "typeof", + "until", + "variant", + "vbasupport", + "wend", + "while", + "with", + "withevents", + "write", + "xor" +}; + + +static const char* strListSqlKeyWords[] = { + "all", + "and", + "any", + "as", + "asc", + "avg", + "between", + "by", + "cast", + "corresponding", + "count", + "create", + "cross", + "delete", + "desc", + "distinct", + "drop", + "escape", + "except", + "exists", + "false", + "from", + "full", + "global", + "group", + "having", + "in", + "inner", + "insert", + "intersect", + "into", + "is", + "join", + "left", + "like", + "limit", + "local", + "match", + "max", + "min", + "natural", + "not", + "null", + "on", + "or", + "order", + "outer", + "right", + "select", + "set", + "some", + "sum", + "table", + "temporary", + "true", + "union", + "unique", + "unknown", + "update", + "using", + "values", + "where" +}; + + +extern "C" { + +static int compare_strings( const void *arg1, const void *arg2 ) +{ + return strcmp( static_cast<char const *>(arg1), *static_cast<char * const *>(arg2) ); +} + +} + +namespace +{ + bool isAlpha(sal_Unicode c) + { + if (rtl::isAsciiAlpha(c)) + return true; + return u_isalpha(c); + } +} + +class SyntaxHighlighter::Tokenizer +{ + // Character information tables + CharFlags aCharTypeTab[256] = {}; + + // Auxiliary function: testing of the character flags + bool testCharFlags(sal_Unicode c, CharFlags nTestFlags) const; + + // Get new token, EmptyString == nothing more over there + bool getNextToken(std::u16string_view::const_iterator& pos, std::u16string_view::const_iterator end, /*out*/TokenType& reType, + /*out*/std::u16string_view::const_iterator& rpStartPos, /*out*/std::u16string_view::const_iterator& rpEndPos) const; + + const char** ppListKeyWords; + sal_uInt16 nKeyWordCount; + +public: + HighlighterLanguage const aLanguage; + + explicit Tokenizer( HighlighterLanguage aLang ); + + void getHighlightPortions(std::u16string_view rLine, + /*out*/std::vector<HighlightPortion>& portions) const; + void setKeyWords( const char** ppKeyWords, sal_uInt16 nCount ); +}; + +// Helper function: test character flag +bool SyntaxHighlighter::Tokenizer::testCharFlags(sal_Unicode c, CharFlags nTestFlags) const +{ + bool bRet = false; + if( c != 0 && c <= 255 ) + { + bRet = bool(aCharTypeTab[c] & nTestFlags); + } + else if( c > 255 ) + { + bRet = (( CharFlags::StartIdentifier | CharFlags::InIdentifier ) & nTestFlags) + && isAlpha(c); + } + return bRet; +} + +void SyntaxHighlighter::Tokenizer::setKeyWords( const char** ppKeyWords, sal_uInt16 nCount ) +{ + ppListKeyWords = ppKeyWords; + nKeyWordCount = nCount; +} + +bool SyntaxHighlighter::Tokenizer::getNextToken(std::u16string_view::const_iterator& pos, std::u16string_view::const_iterator end, + /*out*/TokenType& reType, + /*out*/std::u16string_view::const_iterator& rpStartPos, /*out*/std::u16string_view::const_iterator& rpEndPos) const +{ + reType = TokenType::Unknown; + + rpStartPos = pos; + + if( pos == end ) + return false; + + sal_Unicode c = *pos; + ++pos; + + //*** Go through all possibilities *** + // Space? + if ( testCharFlags( c, CharFlags::Space ) ) + { + while( pos != end && testCharFlags( *pos, CharFlags::Space ) ) + ++pos; + + reType = TokenType::Whitespace; + } + + // Identifier? + else if ( testCharFlags( c, CharFlags::StartIdentifier ) ) + { + bool bIdentifierChar; + do + { + if (pos == end) + break; + // Fetch next character + c = *pos; + bIdentifierChar = testCharFlags( c, CharFlags::InIdentifier ); + if( bIdentifierChar ) + ++pos; + } + while( bIdentifierChar ); + + reType = TokenType::Identifier; + + // Keyword table + if (ppListKeyWords != nullptr) + { + int nCount = pos - rpStartPos; + + // No keyword if string contains char > 255 + bool bCanBeKeyword = true; + for( int i = 0 ; i < nCount ; i++ ) + { + if( rpStartPos[i] > 255 ) + { + bCanBeKeyword = false; + break; + } + } + + if( bCanBeKeyword ) + { + std::u16string_view aKWString(&*rpStartPos, nCount); + OString aByteStr = OUStringToOString(aKWString, + RTL_TEXTENCODING_ASCII_US).toAsciiLowerCase(); + if ( bsearch( aByteStr.getStr(), ppListKeyWords, nKeyWordCount, sizeof( char* ), + compare_strings ) ) + { + reType = TokenType::Keywords; + + if( aByteStr == "rem" ) + { + // Remove all characters until end of line or EOF + for (;;) + { + if (pos == end) + break; + sal_Unicode cPeek = *pos; + if ( testCharFlags( cPeek, CharFlags::EOL ) ) + break; + ++pos; + } + + reType = TokenType::Comment; + } + } + } + } + } + + // Operator? + // only for BASIC '\'' should be a comment, otherwise it is a normal string and handled there + else if ( testCharFlags( c, CharFlags::Operator ) || ( (c == '\'') && (aLanguage==HighlighterLanguage::Basic)) ) + { + // parameters for SQL view + if (((c==':') || (c=='?')) && (aLanguage == HighlighterLanguage::SQL)) + { + if (c!='?') + { + bool bIdentifierChar; + do + { + // Get next character + if (pos == end) + break; + c = *pos; + bIdentifierChar = isAlpha(c); + if( bIdentifierChar ) + ++pos; + } + while( bIdentifierChar ); + } + reType = TokenType::Parameter; + } + else if ((c=='-') && (aLanguage == HighlighterLanguage::SQL)) + { + if (pos != end && *pos=='-') + { + // Remove all characters until end of line or EOF + while( pos != end && !testCharFlags( *pos, CharFlags::EOL ) ) + { + ++pos; + } + reType = TokenType::Comment; + } + else + reType = TokenType::Operator; + } + else if ((c=='/') && (aLanguage == HighlighterLanguage::SQL)) + { + if (pos != end && *pos=='/') + { + // Remove all characters until end of line or EOF + while( pos != end && !testCharFlags( *pos, CharFlags::EOL ) ) + { + ++pos; + } + reType = TokenType::Comment; + } + else + reType = TokenType::Operator; + } + else + { + // Apostrophe is Basic comment + if (( c == '\'') && (aLanguage == HighlighterLanguage::Basic)) + { + // Skip all characters until end of input or end of line: + for (;;) { + if (pos == end) + break; + c = *pos; + if (testCharFlags(c, CharFlags::EOL)) { + break; + } + ++pos; + } + + reType = TokenType::Comment; + } + + // The real operator; can be easily used since not the actual + // operator (e.g. +=) is concerned, but the fact that it is one + if( reType != TokenType::Comment ) + { + reType = TokenType::Operator; + } + + } + } + + // Object separator? Must be handled before Number + else if( c == '.' && ( pos == end || *pos < '0' || *pos > '9' ) ) + { + reType = TokenType::Operator; + } + + // Number? + else if( testCharFlags( c, CharFlags::StartNumber ) ) + { + reType = TokenType::Number; + + // Number system, 10 = normal, it is changed for Oct/Hex + int nRadix = 10; + + // Is it an Oct or a Hex number? + if( c == '&' ) + { + // Octal? + if( pos != end && (*pos == 'o' || *pos == 'O' )) + { + // remove o + ++pos; + nRadix = 8; // Octal base + + // Read all numbers + while( pos != end && testCharFlags( *pos, CharFlags::InOctNumber ) ) + ++pos; + } + // Hexadecimal? + else if( pos != end && (*pos == 'h' || *pos == 'H' )) + { + // remove x + ++pos; + nRadix = 16; // Hexadecimal base + + // Read all numbers + while( pos != end && testCharFlags( *pos, CharFlags::InHexNumber ) ) + ++pos; + } + else + { + reType = TokenType::Operator; + } + } + + // When it is not Oct or Hex, then it is double + if( reType == TokenType::Number && nRadix == 10 ) + { + // Flag if the last character is an exponent + bool bAfterExpChar = false; + + // Read all numbers + while( pos != end && (testCharFlags( *pos, CharFlags::InNumber ) || + (bAfterExpChar && *pos == '+' ) || + (bAfterExpChar && *pos == '-' ) )) + // After exponent +/- are OK, too + { + c = *pos++; + bAfterExpChar = ( c == 'e' || c == 'E' ); + } + } + } + + // String? + else if( testCharFlags( c, CharFlags::StartString ) ) + { + // Remember which character has opened the string + sal_Unicode cEndString = c; + if( c == '[' ) + cEndString = ']'; + + // Read all characters + while( pos == end || *pos != cEndString ) + { + // Detect EOF before reading next char, so we do not lose EOF + if( pos == end ) + { + // ERROR: unterminated string literal + reType = TokenType::Error; + break; + } + c = *pos++; + if( testCharFlags( c, CharFlags::EOL ) ) + { + // ERROR: unterminated string literal + reType = TokenType::Error; + break; + } + } + + if( reType != TokenType::Error ) + { + ++pos; + if( cEndString == ']' ) + reType = TokenType::Identifier; + else + reType = TokenType::String; + } + } + + // End of line? + else if( testCharFlags( c, CharFlags::EOL ) ) + { + // If another EOL character comes, read it + if (pos != end) + { + sal_Unicode cNext = *pos; + if( cNext != c && testCharFlags( cNext, CharFlags::EOL ) ) + ++pos; + } + + reType = TokenType::EOL; + } + + // All other will remain TokenType::Unknown + + // Save end position + rpEndPos = pos; + return true; +} + +SyntaxHighlighter::Tokenizer::Tokenizer( HighlighterLanguage aLang ): aLanguage(aLang) +{ + // Fill character table + sal_uInt16 i; + + // Allowed characters for identifiers + CharFlags nHelpMask = CharFlags::StartIdentifier | CharFlags::InIdentifier; + for( i = 'a' ; i <= 'z' ; i++ ) + aCharTypeTab[i] |= nHelpMask; + for( i = 'A' ; i <= 'Z' ; i++ ) + aCharTypeTab[i] |= nHelpMask; + aCharTypeTab[int('_')] |= nHelpMask; + aCharTypeTab[int('$')] |= nHelpMask; + + // Digit (can be identifier and number) + nHelpMask = CharFlags::InIdentifier | CharFlags::StartNumber | + CharFlags::InNumber | CharFlags::InHexNumber; + for( i = '0' ; i <= '9' ; i++ ) + aCharTypeTab[i] |= nHelpMask; + + // Add e, E, . and & here manually + aCharTypeTab[int('e')] |= CharFlags::InNumber; + aCharTypeTab[int('E')] |= CharFlags::InNumber; + aCharTypeTab[int('.')] |= CharFlags::InNumber | CharFlags::StartNumber; + aCharTypeTab[int('&')] |= CharFlags::StartNumber; + + // Hexadecimal digit + for( i = 'a' ; i <= 'f' ; i++ ) + aCharTypeTab[i] |= CharFlags::InHexNumber; + for( i = 'A' ; i <= 'F' ; i++ ) + aCharTypeTab[i] |= CharFlags::InHexNumber; + + // Octal digit + for( i = '0' ; i <= '7' ; i++ ) + aCharTypeTab[i] |= CharFlags::InOctNumber; + + // String literal start/end characters + aCharTypeTab[int('\'')] |= CharFlags::StartString; + aCharTypeTab[int('\"')] |= CharFlags::StartString; + aCharTypeTab[int('[')] |= CharFlags::StartString; + aCharTypeTab[int('`')] |= CharFlags::StartString; + + // Operator characters + aCharTypeTab[int('!')] |= CharFlags::Operator; + aCharTypeTab[int('%')] |= CharFlags::Operator; + // aCharTypeTab[(int)'&'] |= CharFlags::Operator; Removed because of #i14140 + aCharTypeTab[int('(')] |= CharFlags::Operator; + aCharTypeTab[int(')')] |= CharFlags::Operator; + aCharTypeTab[int('*')] |= CharFlags::Operator; + aCharTypeTab[int('+')] |= CharFlags::Operator; + aCharTypeTab[int(',')] |= CharFlags::Operator; + aCharTypeTab[int('-')] |= CharFlags::Operator; + aCharTypeTab[int('/')] |= CharFlags::Operator; + aCharTypeTab[int(':')] |= CharFlags::Operator; + aCharTypeTab[int('<')] |= CharFlags::Operator; + aCharTypeTab[int('=')] |= CharFlags::Operator; + aCharTypeTab[int('>')] |= CharFlags::Operator; + aCharTypeTab[int('?')] |= CharFlags::Operator; + aCharTypeTab[int('^')] |= CharFlags::Operator; + aCharTypeTab[int('|')] |= CharFlags::Operator; + aCharTypeTab[int('~')] |= CharFlags::Operator; + aCharTypeTab[int('{')] |= CharFlags::Operator; + aCharTypeTab[int('}')] |= CharFlags::Operator; + // aCharTypeTab[(int)'['] |= CharFlags::Operator; Removed because of #i17826 + aCharTypeTab[int(']')] |= CharFlags::Operator; + aCharTypeTab[int(';')] |= CharFlags::Operator; + + // Space + aCharTypeTab[int(' ') ] |= CharFlags::Space; + aCharTypeTab[int('\t')] |= CharFlags::Space; + + // End of line characters + aCharTypeTab[int('\r')] |= CharFlags::EOL; + aCharTypeTab[int('\n')] |= CharFlags::EOL; + + ppListKeyWords = nullptr; + nKeyWordCount = 0; +} + +void SyntaxHighlighter::Tokenizer::getHighlightPortions(std::u16string_view rLine, + /*out*/std::vector<HighlightPortion>& portions) const +{ + // Set the position to the beginning of the source string + auto pos = rLine.begin(); + + // Variables for the out parameter + TokenType eType; + std::u16string_view::const_iterator pStartPos; + std::u16string_view::const_iterator pEndPos; + + // Loop over all the tokens + while( getNextToken( pos, rLine.end(), eType, pStartPos, pEndPos ) ) + { + portions.emplace_back( + pStartPos - rLine.begin(), pEndPos - rLine.begin(), eType); + } +} + + +SyntaxHighlighter::SyntaxHighlighter(HighlighterLanguage language): + m_tokenizer(new SyntaxHighlighter::Tokenizer(language)) +{ + switch (language) + { + case HighlighterLanguage::Basic: + m_tokenizer->setKeyWords( strListBasicKeyWords, + std::size( strListBasicKeyWords )); + break; + case HighlighterLanguage::SQL: + m_tokenizer->setKeyWords( strListSqlKeyWords, + std::size( strListSqlKeyWords )); + break; + default: + assert(false); // this cannot happen + } +} + +SyntaxHighlighter::~SyntaxHighlighter() {} + +void SyntaxHighlighter::getHighlightPortions(std::u16string_view rLine, + /*out*/std::vector<HighlightPortion>& portions) const +{ + m_tokenizer->getHighlightPortions( rLine, portions ); +} + +HighlighterLanguage SyntaxHighlighter::GetLanguage() const +{ + return m_tokenizer->aLanguage; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/threadpool.cxx b/comphelper/source/misc/threadpool.cxx new file mode 100644 index 000000000..f0a71eb05 --- /dev/null +++ b/comphelper/source/misc/threadpool.cxx @@ -0,0 +1,394 @@ +/* -*- 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/. + */ + +#include <comphelper/threadpool.hxx> + +#include <com/sun/star/uno/Exception.hpp> +#include <config_options.h> +#include <o3tl/safeint.hxx> +#include <sal/config.h> +#include <sal/log.hxx> +#include <salhelper/thread.hxx> +#include <algorithm> +#include <memory> +#include <thread> +#include <chrono> +#include <cstddef> +#include <comphelper/debuggerinfo.hxx> +#include <utility> + +#if defined HAVE_VALGRIND_HEADERS +#include <valgrind/memcheck.h> +#endif + +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#endif + +namespace comphelper { + +/** prevent waiting for a task from inside a task */ +#if defined DBG_UTIL && (defined LINUX || defined _WIN32) +static thread_local bool gbIsWorkerThread; +#endif + +// used to group thread-tasks for waiting in waitTillDone() +class ThreadTaskTag +{ + std::mutex maMutex; + sal_Int32 mnTasksWorking; + std::condition_variable maTasksComplete; + +public: + ThreadTaskTag(); + bool isDone(); + void waitUntilDone(); + void onTaskWorkerDone(); + void onTaskPushed(); +}; + + +class ThreadPool::ThreadWorker : public salhelper::Thread +{ + ThreadPool *mpPool; +public: + + explicit ThreadWorker( ThreadPool *pPool ) : + salhelper::Thread("thread-pool"), + mpPool( pPool ) + { + } + + virtual void execute() override + { +#if defined DBG_UTIL && (defined LINUX || defined _WIN32) + gbIsWorkerThread = true; +#endif + std::unique_lock< std::mutex > aGuard( mpPool->maMutex ); + + while( !mpPool->mbTerminate ) + { + std::unique_ptr<ThreadTask> pTask = mpPool->popWorkLocked( aGuard, true ); + if( pTask ) + { + std::shared_ptr<ThreadTaskTag> pTag(pTask->mpTag); + mpPool->incBusyWorker(); + aGuard.unlock(); + + pTask->exec(); + pTask.reset(); + + aGuard.lock(); + mpPool->decBusyWorker(); + pTag->onTaskWorkerDone(); + } + } + } +}; + +ThreadPool::ThreadPool(std::size_t nWorkers) + : mbTerminate(true) + , mnMaxWorkers(nWorkers) + , mnBusyWorkers(0) +{ +} + +ThreadPool::~ThreadPool() +{ + // note: calling shutdown from global variable dtor blocks forever on Win7 + // note2: there isn't enough MSVCRT left on exit to call assert() properly + // so these asserts just print something to stderr but exit status is + // still 0, but hopefully they will be more helpful on non-WNT platforms + assert(mbTerminate); + assert(maTasks.empty()); + assert(mnBusyWorkers == 0); +} + +namespace { + +std::shared_ptr< ThreadPool >& GetStaticThreadPool() +{ + static std::shared_ptr< ThreadPool > POOL = + []() + { + const std::size_t nThreads = ThreadPool::getPreferredConcurrency(); + return std::make_shared< ThreadPool >( nThreads ); + }(); + return POOL; +} + +} + +ThreadPool& ThreadPool::getSharedOptimalPool() +{ + return *GetStaticThreadPool(); +} + +std::size_t ThreadPool::getPreferredConcurrency() +{ + static std::size_t ThreadCount = []() + { + const std::size_t nHardThreads = o3tl::clamp_to_unsigned<std::size_t>( + std::max(std::thread::hardware_concurrency(), 1U)); + std::size_t nThreads = nHardThreads; + const char *pEnv = getenv("MAX_CONCURRENCY"); + if (pEnv != nullptr) + { + // Override with user/admin preference. + nThreads = o3tl::clamp_to_unsigned<std::size_t>(rtl_str_toInt32(pEnv, 10)); + } + + nThreads = std::min(nHardThreads, nThreads); + return std::max<std::size_t>(nThreads, 1); + }(); + + return ThreadCount; +} + +// Used to order shutdown, and to ensure there are no lingering +// threads after LibreOfficeKit pre-init. +void ThreadPool::shutdown() +{ +// if (mbTerminate) +// return; + + std::unique_lock< std::mutex > aGuard( maMutex ); + shutdownLocked(aGuard); +} + +void ThreadPool::shutdownLocked(std::unique_lock<std::mutex>& aGuard) +{ + if( maWorkers.empty() ) + { // no threads at all -> execute the work in-line + std::unique_ptr<ThreadTask> pTask; + while ( ( pTask = popWorkLocked(aGuard, false) ) ) + { + std::shared_ptr<ThreadTaskTag> pTag(pTask->mpTag); + pTask->exec(); + pTag->onTaskWorkerDone(); + } + } + else + { + while( !maTasks.empty() ) + { + maTasksChanged.wait( aGuard ); + // In the (unlikely but possible?) case pushTask() gets called meanwhile, + // its notify_one() call is meant to wake a up a thread and process the task. + // But if this code gets woken up instead, it could lead to a deadlock. + // Pass on the notification. + maTasksChanged.notify_one(); + } + } + assert( maTasks.empty() ); + + // coverity[missing_lock] - on purpose + mbTerminate = true; + + maTasksChanged.notify_all(); + + decltype(maWorkers) aWorkers; + std::swap(maWorkers, aWorkers); + aGuard.unlock(); + + while (!aWorkers.empty()) + { + rtl::Reference<ThreadWorker> xWorker = aWorkers.back(); + aWorkers.pop_back(); + assert(std::find(aWorkers.begin(), aWorkers.end(), xWorker) + == aWorkers.end()); + { + xWorker->join(); + xWorker.clear(); + } + } +} + +void ThreadPool::pushTask( std::unique_ptr<ThreadTask> pTask ) +{ + std::scoped_lock< std::mutex > aGuard( maMutex ); + + mbTerminate = false; + + // Worked on tasks are already removed from maTasks, so include the count of busy workers. + if (maWorkers.size() < mnMaxWorkers && maWorkers.size() <= maTasks.size() + mnBusyWorkers) + { + maWorkers.push_back( new ThreadWorker( this ) ); + maWorkers.back()->launch(); + } + + pTask->mpTag->onTaskPushed(); + maTasks.insert( maTasks.begin(), std::move(pTask) ); + + maTasksChanged.notify_one(); +} + +std::unique_ptr<ThreadTask> ThreadPool::popWorkLocked( std::unique_lock< std::mutex > & rGuard, bool bWait ) +{ + do + { + if( !maTasks.empty() ) + { + std::unique_ptr<ThreadTask> pTask = std::move(maTasks.back()); + maTasks.pop_back(); + return pTask; + } + else if (!bWait || mbTerminate) + return nullptr; + + maTasksChanged.wait( rGuard ); + + } while (!mbTerminate); + + return nullptr; +} + +void ThreadPool::incBusyWorker() +{ + ++mnBusyWorkers; +} + +void ThreadPool::decBusyWorker() +{ + assert(mnBusyWorkers >= 1); + --mnBusyWorkers; +} + +void ThreadPool::waitUntilDone(const std::shared_ptr<ThreadTaskTag>& rTag, bool bJoin) +{ +#if defined DBG_UTIL && (defined LINUX || defined _WIN32) + assert(!gbIsWorkerThread && "cannot wait for tasks from inside a task"); +#endif + { + std::unique_lock< std::mutex > aGuard( maMutex ); + + if( maWorkers.empty() ) + { // no threads at all -> execute the work in-line + while (!rTag->isDone()) + { + std::unique_ptr<ThreadTask> pTask = popWorkLocked(aGuard, false); + if (!pTask) + break; + std::shared_ptr<ThreadTaskTag> pTag(pTask->mpTag); + pTask->exec(); + pTag->onTaskWorkerDone(); + } + } + } + + rTag->waitUntilDone(); + + if (bJoin) + joinThreadsIfIdle(); +} + +void ThreadPool::joinThreadsIfIdle() +{ + std::unique_lock< std::mutex > aGuard( maMutex ); + if (isIdle()) // check if there are still tasks from another tag + { + shutdownLocked(aGuard); + } +} + +std::shared_ptr<ThreadTaskTag> ThreadPool::createThreadTaskTag() +{ + return std::make_shared<ThreadTaskTag>(); +} + +bool ThreadPool::isTaskTagDone(const std::shared_ptr<ThreadTaskTag>& pTag) +{ + return pTag->isDone(); +} + +ThreadTask::ThreadTask(std::shared_ptr<ThreadTaskTag> xTag) + : mpTag(std::move(xTag)) +{ +} + +void ThreadTask::exec() +{ + try { + doWork(); + } + catch (const std::exception &e) + { + SAL_WARN("comphelper", "exception in thread worker while calling doWork(): " << e.what()); + } + catch (const css::uno::Exception &e) + { + SAL_WARN("comphelper", "exception in thread worker while calling doWork(): " << e); + } + catch (...) + { + SAL_WARN("comphelper", "unknown exception in thread worker while calling doWork()"); + } +} + +ThreadTaskTag::ThreadTaskTag() : mnTasksWorking(0) +{ +} + +void ThreadTaskTag::onTaskPushed() +{ + std::scoped_lock< std::mutex > aGuard( maMutex ); + mnTasksWorking++; + assert( mnTasksWorking < 65536 ); // sanity checking +} + +void ThreadTaskTag::onTaskWorkerDone() +{ + std::scoped_lock< std::mutex > aGuard( maMutex ); + mnTasksWorking--; + assert(mnTasksWorking >= 0); + if (mnTasksWorking == 0) + maTasksComplete.notify_all(); +} + +bool ThreadTaskTag::isDone() +{ + std::scoped_lock< std::mutex > aGuard( maMutex ); + return mnTasksWorking == 0; +} + +void ThreadTaskTag::waitUntilDone() +{ + std::unique_lock< std::mutex > aGuard( maMutex ); + while( mnTasksWorking > 0 ) + { +#if defined DBG_UTIL && !defined NDEBUG + // 10 minute timeout in debug mode, unless the code is built with + // sanitizers or debugged in valgrind or gdb, in which case the threads + // should not time out in the middle of a debugging session + int maxTimeout = 10 * 60; +#if !ENABLE_RUNTIME_OPTIMIZATIONS + maxTimeout = 30 * 60; +#endif +#if defined HAVE_VALGRIND_HEADERS + if( RUNNING_ON_VALGRIND ) + maxTimeout = 30 * 60; +#endif + if( isDebuggerAttached()) + maxTimeout = 300 * 60; + std::cv_status result = maTasksComplete.wait_for( + aGuard, std::chrono::seconds( maxTimeout )); + assert(result != std::cv_status::timeout); +#else + // 10 minute timeout in production so the app eventually throws some kind of error + if (maTasksComplete.wait_for( + aGuard, std::chrono::seconds( 10 * 60 )) == std::cv_status::timeout) + throw std::runtime_error("timeout waiting for threadpool tasks"); +#endif + } +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/traceevent.cxx b/comphelper/source/misc/traceevent.cxx new file mode 100644 index 000000000..fb07e1caa --- /dev/null +++ b/comphelper/source/misc/traceevent.cxx @@ -0,0 +1,144 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <atomic> +#include <mutex> +#include <iostream> + +#include <comphelper/profilezone.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/traceevent.hxx> + +namespace comphelper +{ +#ifdef DBG_UTIL +std::atomic<bool> TraceEvent::s_bRecording = (getenv("TRACE_EVENT_RECORDING") != nullptr); +#else +std::atomic<bool> TraceEvent::s_bRecording = false; +#endif + +std::size_t TraceEvent::s_nBufferSize = 0; +void (*TraceEvent::s_pBufferFullCallback)() = nullptr; + +int AsyncEvent::s_nIdCounter = 0; + +static thread_local int nProfileZoneNesting = 0; // Level of Nested Profile Zones + +namespace +{ +std::vector<OUString> g_aRecording; // recorded data +std::mutex g_aMutex; +} + +void TraceEvent::addRecording(const OUString& sObject) +{ + std::lock_guard aGuard(g_aMutex); + + g_aRecording.emplace_back(sObject); + + if (s_nBufferSize > 0 && g_aRecording.size() >= s_nBufferSize) + { + if (s_pBufferFullCallback != nullptr) + (*s_pBufferFullCallback)(); + } +} + +void TraceEvent::addInstantEvent(const char* sName, const std::map<OUString, OUString>& args) +{ + long long nNow = getNow(); + + int nPid = 0; + oslProcessInfo aProcessInfo; + aProcessInfo.Size = sizeof(oslProcessInfo); + if (osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &aProcessInfo) == osl_Process_E_None) + nPid = aProcessInfo.Ident; + + addRecording("{" + "\"name:\"" + + OUString(sName, strlen(sName), RTL_TEXTENCODING_UTF8) + + "\"," + "\"ph\":\"i\"" + + createArgsString(args) + ",\"ts\":" + OUString::number(nNow) + + "," + "\"pid\":" + + OUString::number(nPid) + + "," + "\"tid\":" + + OUString::number(osl_getThreadIdentifier(nullptr)) + "},"); +} + +void TraceEvent::startRecording() +{ + std::lock_guard aGuard(g_aMutex); + s_bRecording = true; +} + +void TraceEvent::stopRecording() { s_bRecording = false; } + +void TraceEvent::setBufferSizeAndCallback(std::size_t bufferSize, void (*bufferFullCallback)()) +{ + s_nBufferSize = bufferSize; + s_pBufferFullCallback = bufferFullCallback; +} + +std::vector<OUString> TraceEvent::getEventVectorAndClear() +{ + bool bRecording; + std::vector<OUString> aRecording; + { + std::lock_guard aGuard(g_aMutex); + bRecording = s_bRecording; + stopRecording(); + aRecording.swap(g_aRecording); + } + // reset start time and nesting level + if (bRecording) + startRecording(); + return aRecording; +} + +css::uno::Sequence<OUString> TraceEvent::getRecordingAndClear() +{ + return comphelper::containerToSequence(getEventVectorAndClear()); +} + +void ProfileZone::addRecording() +{ + assert(s_bRecording); + + long long nNow = getNow(); + + // Generate a single "Complete Event" (type X) + TraceEvent::addRecording("{" + "\"name\":\"" + + OUString(m_sName, strlen(m_sName), RTL_TEXTENCODING_UTF8) + + "\"," + "\"ph\":\"X\"," + "\"ts\":" + + OUString::number(m_nCreateTime) + + "," + "\"dur\":" + + OUString::number(nNow - m_nCreateTime) + m_sArgs + + "," + "\"pid\":" + + OUString::number(m_nPid) + + "," + "\"tid\":" + + OUString::number(osl_getThreadIdentifier(nullptr)) + "},"); +} + +int ProfileZone::getNestingLevel() { return nProfileZoneNesting; } + +void ProfileZone::setNestingLevel(int nNestingLevel) { nProfileZoneNesting = nNestingLevel; } + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/typedescriptionref.hxx b/comphelper/source/misc/typedescriptionref.hxx new file mode 100644 index 000000000..f4580cb2e --- /dev/null +++ b/comphelper/source/misc/typedescriptionref.hxx @@ -0,0 +1,51 @@ +/* -*- 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 . + */ + +#pragma once + +#include <typelib/typedescription.h> + +namespace comphelper::detail +{ +// This is like com::sun::star::uno::TypeDescription, but it uses TYPELIB_DANGER_GET +// (which the code used originally, but it's easier to have a class to handle ownership). +class TypeDescriptionRef +{ +public: + TypeDescriptionRef(typelib_TypeDescriptionReference* typeDef) + { + TYPELIB_DANGER_GET(&typeDescr, typeDef); + } + ~TypeDescriptionRef() { TYPELIB_DANGER_RELEASE(typeDescr); } + typelib_TypeDescription* get() { return typeDescr; } + typelib_TypeDescription* operator->() { return typeDescr; } + bool is() { return typeDescr != nullptr; } + bool equals(const TypeDescriptionRef& other) const + { + return typeDescr && other.typeDescr + && typelib_typedescription_equals(typeDescr, other.typeDescr); + } + +private: + typelib_TypeDescription* typeDescr = nullptr; +}; + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/types.cxx b/comphelper/source/misc/types.cxx new file mode 100644 index 000000000..8887d5b5a --- /dev/null +++ b/comphelper/source/misc/types.cxx @@ -0,0 +1,146 @@ +/* -*- 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/types.hxx> +#include <comphelper/extract.hxx> +#include <com/sun/star/awt/FontUnderline.hpp> +#include <com/sun/star/awt/FontStrikeout.hpp> +#include <com/sun/star/awt/FontDescriptor.hpp> +#include <o3tl/any.hxx> +#include <osl/diagnose.h> +#include <typelib/typedescription.hxx> +#include <sal/log.hxx> + +namespace comphelper +{ +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::lang; + +sal_Int64 getINT64(const Any& _rAny) +{ + sal_Int64 nReturn = 0; + if (!(_rAny >>= nReturn)) + SAL_WARN("comphelper", "conversion from Any to sal_Int64 failed"); + return nReturn; +} + +sal_Int32 getINT32(const Any& _rAny) +{ + sal_Int32 nReturn = 0; + if (!(_rAny >>= nReturn)) + SAL_WARN("comphelper", "conversion from Any to sal_Int32 failed"); + return nReturn; +} + +sal_Int16 getINT16(const Any& _rAny) +{ + sal_Int16 nReturn = 0; + if (!(_rAny >>= nReturn)) + SAL_WARN("comphelper", "conversion from Any to sal_Int16 failed"); + return nReturn; +} + +double getDouble(const Any& _rAny) +{ + double nReturn = 0.0; + if (!(_rAny >>= nReturn)) + SAL_WARN("comphelper", "conversion from Any to double failed"); + return nReturn; +} + +float getFloat(const Any& _rAny) +{ + float nReturn = 0.0; + if (!(_rAny >>= nReturn)) + SAL_WARN("comphelper", "conversion from Any to float failed"); + return nReturn; +} + +OUString getString(const Any& _rAny) +{ + OUString nReturn; + if (!(_rAny >>= nReturn)) + SAL_WARN("comphelper", "conversion from Any to OUString failed"); + return nReturn; +} + +bool getBOOL(const Any& _rAny) +{ + bool bReturn = false; + if (auto b = o3tl::tryAccess<bool>(_rAny)) + bReturn = *b; + else + OSL_FAIL("comphelper::getBOOL : invalid argument !"); + return bReturn; +} + +sal_Int32 getEnumAsINT32(const Any& _rAny) +{ + sal_Int32 nReturn = 0; + if (!::cppu::enum2int(nReturn, _rAny)) + throw IllegalArgumentException("enum2int failed", + css::uno::Reference<css::uno::XInterface>(), -1); + return nReturn; +} + +FontDescriptor getDefaultFont() +{ + FontDescriptor aReturn; + aReturn.Slant = FontSlant_DONTKNOW; + aReturn.Underline = FontUnderline::DONTKNOW; + aReturn.Strikeout = com::sun::star::awt::FontStrikeout::DONTKNOW; + return aReturn; +} + +bool isAssignableFrom(const Type& _rAssignable, const Type& _rFrom) +{ + // get the type lib descriptions + typelib_TypeDescription* pAssignable = nullptr; + _rAssignable.getDescription(&pAssignable); + + typelib_TypeDescription* pFrom = nullptr; + _rFrom.getDescription(&pFrom); + + // and ask the type lib + return typelib_typedescription_isAssignableFrom(pAssignable, pFrom); +} + +Type getSequenceElementType(const Type& _rSequenceType) +{ + OSL_ENSURE(_rSequenceType.getTypeClass() == TypeClass_SEQUENCE, + "getSequenceElementType: must be called with a sequence type!"); + + if (_rSequenceType.getTypeClass() != TypeClass_SEQUENCE) + return Type(); + + TypeDescription aTD(_rSequenceType); + typelib_IndirectTypeDescription* pSequenceTD + = reinterpret_cast<typelib_IndirectTypeDescription*>(aTD.get()); + + OSL_ASSERT(pSequenceTD && pSequenceTD->pType); + if (pSequenceTD && pSequenceTD->pType) + return Type(pSequenceTD->pType); + + return Type(); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/weak.cxx b/comphelper/source/misc/weak.cxx new file mode 100644 index 000000000..02cf40e2b --- /dev/null +++ b/comphelper/source/misc/weak.cxx @@ -0,0 +1,57 @@ +/* -*- 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/weak.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; + +namespace comphelper +{ + +OWeakTypeObject::OWeakTypeObject() +{ +} + +OWeakTypeObject::~OWeakTypeObject() +{ +} + +Any SAL_CALL OWeakTypeObject::queryInterface(const Type & rType ) +{ + if( rType == cppu::UnoType<XTypeProvider>::get() ) + return Any( Reference< XTypeProvider >(this) ); + else + return ::cppu::OWeakObject::queryInterface( rType ); +} + +Sequence< Type > SAL_CALL OWeakTypeObject::getTypes( ) +{ + return Sequence< Type >(); +} + +Sequence< ::sal_Int8 > SAL_CALL OWeakTypeObject::getImplementationId( ) +{ + return Sequence< ::sal_Int8 >(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/weakeventlistener.cxx b/comphelper/source/misc/weakeventlistener.cxx new file mode 100644 index 000000000..e8ce46590 --- /dev/null +++ b/comphelper/source/misc/weakeventlistener.cxx @@ -0,0 +1,74 @@ +/* -*- 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/weakeventlistener.hxx> +#include <osl/diagnose.h> + + +namespace comphelper +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + + OWeakListenerAdapterBase::~OWeakListenerAdapterBase() + { + } + + OWeakEventListenerAdapter::OWeakEventListenerAdapter( Reference< XWeak > const & _rxListener, Reference< XComponent > const & _rxBroadcaster ) + :OWeakEventListenerAdapter_Base( _rxListener, _rxBroadcaster ) + { + // add ourself as listener to the broadcaster + OSL_ENSURE( _rxBroadcaster.is(), "OWeakEventListenerAdapter::OWeakEventListenerAdapter: invalid broadcaster!" ); + if ( _rxBroadcaster.is() ) + { + osl_atomic_increment( &m_refCount ); + { + _rxBroadcaster->addEventListener( this ); + } + osl_atomic_decrement( &m_refCount ); + OSL_ENSURE( m_refCount > 0, "OWeakEventListenerAdapter::OWeakEventListenerAdapter: oops - not to be used with implementations which hold their listeners weak!" ); + // the one and only reason for this adapter class (A) is to add as listener to a component (C) which + // holds its listeners hard, and forward all calls then to another listener (L) which is + // held weak by A. + // Now if C holds listeners weak, then we do not need A, we can add L directly to C. + } + + OSL_ENSURE( getListener().is(), "OWeakEventListenerAdapter::OWeakEventListenerAdapter: invalid listener (does not support the XEventListener interface)!" ); + } + + + void SAL_CALL OWeakEventListenerAdapter::disposing( ) + { + Reference< XComponent > xBroadcaster( getBroadcaster( ), UNO_QUERY ); + OSL_ENSURE( xBroadcaster.is(), "OWeakEventListenerAdapter::disposing: broadcaster is invalid in the meantime! How this?" ); + if ( xBroadcaster.is() ) + { + xBroadcaster->removeEventListener( this ); + } + + resetListener(); + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/misc/xmlsechelper.cxx b/comphelper/source/misc/xmlsechelper.cxx new file mode 100644 index 000000000..5b1a438ab --- /dev/null +++ b/comphelper/source/misc/xmlsechelper.cxx @@ -0,0 +1,319 @@ +/* -*- 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/xmlsechelper.hxx> + +#include <rtl/ustrbuf.hxx> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> + +#include <utility> +#include <vector> + +namespace comphelper::xmlsec +{ + OUString GetCertificateKind( const css::security::CertificateKind &rKind ) + { + switch (rKind) + { + case css::security::CertificateKind_X509: + return "X.509"; + case css::security::CertificateKind_OPENPGP: + return "OpenPGP"; + default: + return OUString(); + } + } + + /* + Creates two strings based on the distinguished name which are displayed in the + certificate details view. The first string contains only the values of the attribute + and values pairs, which are separated by commas. All escape characters ('"') are + removed. + The second string is for the details view at the bottom. It shows the attribute/value + pairs on different lines. All escape characters ('"') are removed. + */ + std::pair< OUString, OUString> GetDNForCertDetailsView( std::u16string_view rRawString) + { + std::vector< std::pair< OUString, OUString > > vecAttrValueOfDN = parseDN(rRawString); + OUStringBuffer s1, s2; + for (auto i = vecAttrValueOfDN.cbegin(); i < vecAttrValueOfDN.cend(); ++i) + { + if (i != vecAttrValueOfDN.cbegin()) + { + s1.append(','); + s2.append('\n'); + } + s1.append(i->second); + s2.append(i->first + " = " + i->second); + } + return std::make_pair(s1.makeStringAndClear(), s2.makeStringAndClear()); + } + +/* + Whenever the attribute value contains special characters, such as '"' or ',' (without '') + then the value will be enclosed in double quotes by the respective Windows or NSS function + which we use to retrieve, for example, the subject name. If double quotes appear in the value then + they are escaped with a double quote. This function removes the escape characters. +*/ +#ifdef _WIN32 +std::vector< std::pair< OUString, OUString> > parseDN(std::u16string_view rRawString) +{ + std::vector< std::pair<OUString, OUString> > retVal; + bool bInEscape = false; + bool bInValue = false; + bool bInType = true; + sal_Int32 nTypeNameStart = 0; + std::u16string_view sType; + OUStringBuffer sbufValue; + size_t length = rRawString.size(); + + for (size_t i = 0; i < length; i++) + { + sal_Unicode c = rRawString[i]; + + if (c == '=') + { + if (! bInValue) + { + sType = rRawString.substr(nTypeNameStart, i - nTypeNameStart); + sType = o3tl::trim(sType); + bInType = false; + } + else + { + sbufValue.append(c); + } + } + else if (c == '"') + { + if (!bInEscape) + { + //If this is the quote is the first of the couple which enclose the + //whole value, because the value contains special characters + //then we just drop it. That is, this character must be followed by + //a character which is not '"'. + if ( i + 1 < length && rRawString[i+1] == '"') + bInEscape = true; + else + bInValue = !bInValue; //value is enclosed in " " + } + else + { + //This quote is escaped by a preceding quote and therefore is + //part of the value + sbufValue.append(c); + bInEscape = false; + } + } + else if (c == ',' || c == '+') + { + //The comma separate the attribute value pairs. + //If the comma is not part of a value (the value would then be enclosed in '"'), + //then we have reached the end of the value + if (!bInValue) + { + OSL_ASSERT(!sType.empty()); + retVal.push_back(std::make_pair(OUString(sType), sbufValue.makeStringAndClear())); + sType = {}; + //The next char is the start of the new type + nTypeNameStart = i + 1; + bInType = true; + } + else + { + //The whole string is enclosed because it contains special characters. + //The enclosing '"' are not part of certificate but will be added by + //the function (Windows or NSS) which retrieves DN + sbufValue.append(c); + } + } + else + { + if (!bInType) + sbufValue.append(c); + } + } + if (sbufValue.getLength()) + { + OSL_ASSERT(!sType.empty()); + retVal.push_back(std::make_pair(OUString(sType), sbufValue.makeStringAndClear())); + } + return retVal; + } +#else +std::vector< std::pair< OUString, OUString> > parseDN(std::u16string_view rRawString) + { + std::vector< std::pair<OUString, OUString> > retVal; + //bInEscape == true means that the preceding character is an escape character + bool bInEscape = false; + bool bInValue = false; + bool bInType = true; + sal_Int32 nTypeNameStart = 0; + std::u16string_view sType; + OUStringBuffer sbufValue; + size_t length = rRawString.size(); + + for (size_t i = 0; i < length; i++) + { + sal_Unicode c = rRawString[i]; + + if (c == '=') + { + if (! bInValue) + { + sType = rRawString.substr(nTypeNameStart, i - nTypeNameStart); + sType = o3tl::trim(sType); + bInType = false; + } + else + { + sbufValue.append(c); + } + } + else if (c == '\\') + { + if (!bInEscape) + { + bInEscape = true; + } + else + { // bInEscape is true + sbufValue.append(c); + bInEscape = false; + } + } + else if (c == '"') + { + //an unescaped '"' is either at the beginning or end of the value + if (!bInEscape) + { + if ( !bInValue) + bInValue = true; + else if (bInValue) + bInValue = false; + } + else + { + //This quote is escaped by a preceding quote and therefore is + //part of the value + sbufValue.append(c); + bInEscape = false; + } + } + else if (c == ',' || c == '+') + { + //The comma separate the attribute value pairs. + //If the comma is not part of a value (the value would then be enclosed in '"'), + //then we have reached the end of the value + if (!bInValue) + { + OSL_ASSERT(!sType.empty()); + retVal.emplace_back(sType, sbufValue.makeStringAndClear()); + sType = {}; + //The next char is the start of the new type + nTypeNameStart = i + 1; + bInType = true; + } + else + { + //The whole string is enclosed because it contains special characters. + //The enclosing '"' are not part of certificate but will be added by + //the function (Windows or NSS) which retrieves DN + sbufValue.append(c); + } + } + else + { + if (!bInType) + { + sbufValue.append(c); + bInEscape = false; + } + } + } + if (!sbufValue.isEmpty()) + { + OSL_ASSERT(!sType.empty()); + retVal.emplace_back(sType, sbufValue.makeStringAndClear()); + } + return retVal; + } + +#endif + + OUString GetContentPart( const OUString& _rRawString, const css::security::CertificateKind &rKind ) + { + char const * aIDs[] = { "CN", "OU", "O", "E", nullptr }; + + // tdf#131733 Don't process OpenPGP certs, only X509 + if (rKind == css::security::CertificateKind_OPENPGP ) + return _rRawString; + + OUString retVal; + int i = 0; + std::vector< std::pair< OUString, OUString > > vecAttrValueOfDN = parseDN(_rRawString); + while ( aIDs[i] ) + { + OUString sPartId = OUString::createFromAscii( aIDs[i++] ); + auto idn = std::find_if(vecAttrValueOfDN.cbegin(), vecAttrValueOfDN.cend(), + [&sPartId](const std::pair< OUString, OUString >& dn) { return dn.first == sPartId; }); + if (idn != vecAttrValueOfDN.cend()) + retVal = idn->second; + if (!retVal.isEmpty()) + break; + } + return retVal.isEmpty() ? _rRawString : retVal; + } + + OUString GetHexString( const css::uno::Sequence< sal_Int8 >& _rSeq, const char* _pSep, sal_uInt16 _nLineBreak ) + { + const sal_Int8* pSerNumSeq = _rSeq.getConstArray(); + int nCnt = _rSeq.getLength(); + OUStringBuffer aStr; + const char pHexDigs[ 17 ] = "0123456789ABCDEF"; + char pBuffer[ 3 ] = " "; + sal_uInt8 nNum; + sal_uInt16 nBreakStart = _nLineBreak? _nLineBreak : 1; + sal_uInt16 nBreak = nBreakStart; + for( int i = 0 ; i < nCnt ; ++i ) + { + nNum = sal_uInt8( pSerNumSeq[ i ] ); + + // exchange the buffer[0] and buffer[1], which make it consistent with Mozilla and Windows + pBuffer[ 1 ] = pHexDigs[ nNum & 0x0F ]; + nNum >>= 4; + pBuffer[ 0 ] = pHexDigs[ nNum ]; + aStr.appendAscii( pBuffer ); + + --nBreak; + if( nBreak ) + aStr.appendAscii( _pSep ); + else + { + nBreak = nBreakStart; + aStr.append( '\n' ); + } + } + + return aStr.makeStringAndClear(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/officeinstdir/officeinstallationdirectories.cxx b/comphelper/source/officeinstdir/officeinstallationdirectories.cxx new file mode 100644 index 000000000..4cb3eb5e3 --- /dev/null +++ b/comphelper/source/officeinstdir/officeinstallationdirectories.cxx @@ -0,0 +1,249 @@ +/* -*- 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 <config_folders.h> + +#include <cppuhelper/supportsservice.hxx> + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ + +#include <osl/file.hxx> +#include <com/sun/star/util/theMacroExpander.hpp> +#include <comphelper/fileurl.hxx> +#include <utility> + +#include "officeinstallationdirectories.hxx" + +using namespace com::sun::star; + +static bool makeCanonicalFileURL( OUString & rURL ) +{ + OSL_ENSURE(comphelper::isFileUrl(rURL), "File URL expected!"); + + OUString aNormalizedURL; + if ( osl::FileBase::getAbsoluteFileURL( OUString(), + rURL, + aNormalizedURL ) + != osl::DirectoryItem::E_None ) + return false; + + osl::DirectoryItem aDirItem; + if ( osl::DirectoryItem::get( aNormalizedURL, aDirItem ) + != osl::DirectoryItem::E_None ) + return false; + + osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileURL ); + + if ( aDirItem.getFileStatus( aFileStatus ) + == osl::DirectoryItem::E_None ) + { + aNormalizedURL = aFileStatus.getFileURL(); + + if ( !aNormalizedURL.isEmpty() ) + { + if ( !aNormalizedURL.endsWith("/") ) + rURL = aNormalizedURL; + else + rURL = aNormalizedURL + .copy( 0, aNormalizedURL.getLength() - 1 ); + + return true; + } + } + return false; +} + +namespace comphelper { + +constexpr OUStringLiteral g_aOfficeBrandDirMacro(u"$(brandbaseurl)"); +constexpr OUStringLiteral g_aUserDirMacro(u"$(userdataurl)"); + +OfficeInstallationDirectories::OfficeInstallationDirectories( + uno::Reference< uno::XComponentContext > xCtx ) +: m_xCtx(std::move( xCtx )) +{ +} + + +// virtual +OfficeInstallationDirectories::~OfficeInstallationDirectories() +{ +} + + +// util::XOfficeInstallationDirectories + + +// virtual +OUString SAL_CALL +OfficeInstallationDirectories::getOfficeInstallationDirectoryURL() +{ + initDirs(); + return *m_xOfficeBrandDir; +} + + +// virtual +OUString SAL_CALL +OfficeInstallationDirectories::getOfficeUserDataDirectoryURL() +{ + initDirs(); + return *m_xUserDir; +} + + + +// virtual +OUString SAL_CALL +OfficeInstallationDirectories::makeRelocatableURL( const OUString& URL ) +{ + if ( !URL.isEmpty() ) + { + initDirs(); + + OUString aCanonicalURL( URL ); + makeCanonicalFileURL( aCanonicalURL ); + + sal_Int32 nIndex = aCanonicalURL.indexOf( *m_xOfficeBrandDir ); + if ( nIndex != -1 ) + { + return + aCanonicalURL.replaceAt( nIndex, + m_xOfficeBrandDir->getLength(), + g_aOfficeBrandDirMacro ); + } + else + { + nIndex = aCanonicalURL.indexOf( *m_xUserDir ); + if ( nIndex != -1 ) + { + return + aCanonicalURL.replaceAt( nIndex, + m_xUserDir->getLength(), + g_aUserDirMacro ); + } + } + } + return URL; +} + + +// virtual +OUString SAL_CALL +OfficeInstallationDirectories::makeAbsoluteURL( const OUString& URL ) +{ + if ( !URL.isEmpty() ) + { + sal_Int32 nIndex = URL.indexOf( g_aOfficeBrandDirMacro ); + if ( nIndex != -1 ) + { + initDirs(); + + return + URL.replaceAt( nIndex, + g_aOfficeBrandDirMacro.getLength(), + *m_xOfficeBrandDir ); + } + else + { + nIndex = URL.indexOf( g_aUserDirMacro ); + if ( nIndex != -1 ) + { + initDirs(); + + return + URL.replaceAt( nIndex, + g_aUserDirMacro.getLength(), + *m_xUserDir ); + } + } + } + return URL; +} + + +// lang::XServiceInfo + + +// virtual +OUString SAL_CALL +OfficeInstallationDirectories::getImplementationName() +{ + return "com.sun.star.comp.util.OfficeInstallationDirectories"; +} + +// virtual +sal_Bool SAL_CALL +OfficeInstallationDirectories::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +// virtual +uno::Sequence< OUString > SAL_CALL +OfficeInstallationDirectories::getSupportedServiceNames() +{ + return { "com.sun.star.util.OfficeInstallationDirectories" }; +} + +void OfficeInstallationDirectories::initDirs() +{ + if ( m_xOfficeBrandDir) + return; + + osl::MutexGuard aGuard( m_aMutex ); + if ( m_xOfficeBrandDir ) + return; + + uno::Reference< util::XMacroExpander > xExpander = util::theMacroExpander::get(m_xCtx); + + m_xOfficeBrandDir = xExpander->expandMacros( "$BRAND_BASE_DIR" ); + + OSL_ENSURE( !m_xOfficeBrandDir->isEmpty(), + "Unable to obtain office brand installation directory!" ); + + makeCanonicalFileURL( *m_xOfficeBrandDir ); + + m_xUserDir = + xExpander->expandMacros( + "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap" ) ":UserInstallation}" ); + + OSL_ENSURE( !m_xUserDir->isEmpty(), + "Unable to obtain office user data directory!" ); + + makeCanonicalFileURL( *m_xUserDir ); +} + +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_util_OfficeInstallationDirectories( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire( + new comphelper::OfficeInstallationDirectories(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/officeinstdir/officeinstallationdirectories.hxx b/comphelper/source/officeinstdir/officeinstallationdirectories.hxx new file mode 100644 index 000000000..cc0881c23 --- /dev/null +++ b/comphelper/source/officeinstdir/officeinstallationdirectories.hxx @@ -0,0 +1,75 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/util/XOfficeInstallationDirectories.hpp> + +#include <optional> + +namespace com::sun::star::uno { class XComponentContext; } + +namespace comphelper { + + + +typedef cppu::WeakImplHelper< + css::util::XOfficeInstallationDirectories, + css::lang::XServiceInfo > UnoImplBase; + +class OfficeInstallationDirectories : public cppu::BaseMutex, public UnoImplBase +{ +public: + explicit OfficeInstallationDirectories( + css::uno::Reference< css::uno::XComponentContext > xCtx ); + virtual ~OfficeInstallationDirectories() override; + + // XOfficeInstallationDirectories + virtual OUString SAL_CALL + getOfficeInstallationDirectoryURL() override; + virtual OUString SAL_CALL + getOfficeUserDataDirectoryURL() override; + virtual OUString SAL_CALL + makeRelocatableURL( const OUString& URL ) override; + virtual OUString SAL_CALL + makeAbsoluteURL( const OUString& URL ) 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: + void initDirs(); + + css::uno::Reference< css::uno::XComponentContext > m_xCtx; + std::optional<OUString> m_xOfficeBrandDir; + std::optional<OUString> m_xUserDir; +}; + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/processfactory/processfactory.cxx b/comphelper/source/processfactory/processfactory.cxx new file mode 100644 index 000000000..fc8586471 --- /dev/null +++ b/comphelper/source/processfactory/processfactory.cxx @@ -0,0 +1,117 @@ +/* -*- 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 <mutex> +#include <comphelper/processfactory.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/uno/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +namespace com::sun::star::uno { class XComponentContext; } + +using namespace ::com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace osl; + +namespace comphelper +{ + +namespace { + +class LocalProcessFactory { +public: + void set( const Reference< XMultiServiceFactory >& xSMgr ) + { + std::unique_lock aGuard( maMutex ); + + xProcessFactory = xSMgr; + } + + Reference< XMultiServiceFactory > get() const + { + std::unique_lock aGuard( maMutex ); + + return xProcessFactory; + } + +private: + mutable std::mutex maMutex; + Reference< XMultiServiceFactory > xProcessFactory; +}; + +/* + This var preserves only that the above xProcessFactory variable will not be create when + the library is loaded. +*/ +LocalProcessFactory localProcessFactory; + +} + +void setProcessServiceFactory(const Reference< XMultiServiceFactory >& xSMgr) +{ + localProcessFactory.set( xSMgr ); +} + +Reference< XMultiServiceFactory > getProcessServiceFactory() +{ + Reference< XMultiServiceFactory> xReturn = localProcessFactory.get(); + if ( !xReturn.is() ) + { + throw DeploymentException( "null process service factory" ); + } + return xReturn; +} + +Reference< XComponentContext > getComponentContext( + Reference< XMultiServiceFactory > const & factory) +{ + Reference< XComponentContext > xRet; + uno::Reference<beans::XPropertySet> const xProps( factory, uno::UNO_QUERY ); + if (xProps.is()) { + static constexpr OUStringLiteral DEFAULT_CONTEXT = u"DefaultContext"; + try { + xRet.set( xProps->getPropertyValue(DEFAULT_CONTEXT), + uno::UNO_QUERY ); + } + catch (beans::UnknownPropertyException & e) { + throw DeploymentException( + "unknown service factory DefaultContext property: " + e.Message, + Reference<XInterface>(factory, UNO_QUERY) ); + } + } + if ( !xRet.is() ) + { + throw DeploymentException( + "no service factory DefaultContext", + Reference<XInterface>(factory, UNO_QUERY) ); + } + return xRet; +} + +Reference< XComponentContext > getProcessComponentContext() +{ + return getComponentContext( getProcessServiceFactory() ); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/ChainablePropertySet.cxx b/comphelper/source/property/ChainablePropertySet.cxx new file mode 100644 index 000000000..0805afe72 --- /dev/null +++ b/comphelper/source/property/ChainablePropertySet.cxx @@ -0,0 +1,239 @@ +/* -*- 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/ChainablePropertySet.hxx> +#include <comphelper/ChainablePropertySetInfo.hxx> +#include <comphelper/solarmutex.hxx> + + +#include <memory> +#include <optional> + +using namespace ::comphelper; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; + +ChainablePropertySet::ChainablePropertySet( comphelper::ChainablePropertySetInfo* pInfo, comphelper::SolarMutex* pMutex ) + noexcept +: mpMutex ( pMutex ) +, mxInfo ( pInfo ) +{ +} + +ChainablePropertySet::~ChainablePropertySet() + noexcept +{ +} + +// XPropertySet +Reference< XPropertySetInfo > SAL_CALL ChainablePropertySet::getPropertySetInfo( ) +{ + return mxInfo; +} + +void SAL_CALL ChainablePropertySet::setPropertyValue( const OUString& rPropertyName, const Any& rValue ) +{ + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + std::optional< osl::Guard< comphelper::SolarMutex > > xMutexGuard; + if (mpMutex) + xMutexGuard.emplace( mpMutex ); + + PropertyInfoHash::const_iterator aIter = mxInfo->maMap.find ( rPropertyName ); + + if( aIter == mxInfo->maMap.end()) + throw UnknownPropertyException( rPropertyName, static_cast< XPropertySet* >( this ) ); + + _preSetValues(); + _setSingleValue( *((*aIter).second), rValue ); + _postSetValues(); +} + +Any SAL_CALL ChainablePropertySet::getPropertyValue( const OUString& rPropertyName ) +{ + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + std::optional< osl::Guard< comphelper::SolarMutex > > xMutexGuard; + if (mpMutex) + xMutexGuard.emplace( mpMutex ); + + PropertyInfoHash::const_iterator aIter = mxInfo->maMap.find ( rPropertyName ); + + if( aIter == mxInfo->maMap.end()) + throw UnknownPropertyException( rPropertyName, static_cast< XPropertySet* >( this ) ); + + Any aAny; + _preGetValues (); + _getSingleValue( *((*aIter).second), aAny ); + _postGetValues (); + + return aAny; +} + +void SAL_CALL ChainablePropertySet::addPropertyChangeListener( const OUString&, const Reference< XPropertyChangeListener >& ) +{ + // todo +} + +void SAL_CALL ChainablePropertySet::removePropertyChangeListener( const OUString&, const Reference< XPropertyChangeListener >& ) +{ + // todo +} + +void SAL_CALL ChainablePropertySet::addVetoableChangeListener( const OUString&, const Reference< XVetoableChangeListener >& ) +{ + // todo +} + +void SAL_CALL ChainablePropertySet::removeVetoableChangeListener( const OUString&, const Reference< XVetoableChangeListener >& ) +{ + // todo +} + +// XMultiPropertySet +void SAL_CALL ChainablePropertySet::setPropertyValues(const Sequence< OUString >& rPropertyNames, const Sequence< Any >& rValues) +{ + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + std::optional< osl::Guard< comphelper::SolarMutex > > xMutexGuard; + if (mpMutex) + xMutexGuard.emplace( mpMutex ); + + const sal_Int32 nCount = rPropertyNames.getLength(); + + if( nCount != rValues.getLength() ) + throw IllegalArgumentException("lengths do not match", static_cast<cppu::OWeakObject*>(this), -1); + + if( !nCount ) + return; + + _preSetValues(); + + const Any * pAny = rValues.getConstArray(); + const OUString * pString = rPropertyNames.getConstArray(); + PropertyInfoHash::const_iterator aEnd = mxInfo->maMap.end(), aIter; + + for ( sal_Int32 i = 0; i < nCount; ++i, ++pString, ++pAny ) + { + aIter = mxInfo->maMap.find ( *pString ); + if ( aIter == aEnd ) + throw RuntimeException( *pString, static_cast< XPropertySet* >( this ) ); + + _setSingleValue ( *((*aIter).second), *pAny ); + } + + _postSetValues(); +} + +Sequence< Any > SAL_CALL ChainablePropertySet::getPropertyValues(const Sequence< OUString >& rPropertyNames) +{ + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + std::optional< osl::Guard< comphelper::SolarMutex > > xMutexGuard; + if (mpMutex) + xMutexGuard.emplace( mpMutex ); + + const sal_Int32 nCount = rPropertyNames.getLength(); + + Sequence < Any > aValues ( nCount ); + + if( nCount ) + { + _preGetValues(); + + Any * pAny = aValues.getArray(); + const OUString * pString = rPropertyNames.getConstArray(); + PropertyInfoHash::const_iterator aEnd = mxInfo->maMap.end(), aIter; + + for ( sal_Int32 i = 0; i < nCount; ++i, ++pString, ++pAny ) + { + aIter = mxInfo->maMap.find ( *pString ); + if ( aIter == aEnd ) + throw RuntimeException( *pString, static_cast< XPropertySet* >( this ) ); + + _getSingleValue ( *((*aIter).second), *pAny ); + } + + _postGetValues(); + } + return aValues; +} + +void SAL_CALL ChainablePropertySet::addPropertiesChangeListener( const Sequence< OUString >&, const Reference< XPropertiesChangeListener >& ) +{ + // todo +} + +void SAL_CALL ChainablePropertySet::removePropertiesChangeListener( const Reference< XPropertiesChangeListener >& ) +{ + // todo +} + +void SAL_CALL ChainablePropertySet::firePropertiesChangeEvent( const Sequence< OUString >&, const Reference< XPropertiesChangeListener >& ) +{ + // todo +} + +// XPropertyState +PropertyState SAL_CALL ChainablePropertySet::getPropertyState( const OUString& PropertyName ) +{ + PropertyInfoHash::const_iterator aIter = mxInfo->maMap.find( PropertyName ); + if( aIter == mxInfo->maMap.end()) + throw UnknownPropertyException( PropertyName, static_cast< XPropertySet* >( this ) ); + + return PropertyState_AMBIGUOUS_VALUE; +} + +Sequence< PropertyState > SAL_CALL ChainablePropertySet::getPropertyStates( const Sequence< OUString >& rPropertyNames ) +{ + const sal_Int32 nCount = rPropertyNames.getLength(); + + Sequence< PropertyState > aStates( nCount ); + if( nCount ) + { + PropertyState * pState = aStates.getArray(); + const OUString * pString = rPropertyNames.getConstArray(); + PropertyInfoHash::const_iterator aEnd = mxInfo->maMap.end(), aIter; + + for ( sal_Int32 i = 0; i < nCount; ++i, ++pString, ++pState ) + { + aIter = mxInfo->maMap.find ( *pString ); + if ( aIter == aEnd ) + throw UnknownPropertyException( *pString, static_cast< XPropertySet* >( this ) ); + } + } + return aStates; +} + +void SAL_CALL ChainablePropertySet::setPropertyToDefault( const OUString& rPropertyName ) +{ + PropertyInfoHash::const_iterator aIter = mxInfo->maMap.find ( rPropertyName ); + + if( aIter == mxInfo->maMap.end()) + throw UnknownPropertyException( rPropertyName, static_cast< XPropertySet* >( this ) ); +} + +Any SAL_CALL ChainablePropertySet::getPropertyDefault( const OUString& rPropertyName ) +{ + PropertyInfoHash::const_iterator aIter = mxInfo->maMap.find ( rPropertyName ); + + if( aIter == mxInfo->maMap.end()) + throw UnknownPropertyException( rPropertyName, static_cast< XPropertySet* >( this ) ); + return Any(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/ChainablePropertySetInfo.cxx b/comphelper/source/property/ChainablePropertySetInfo.cxx new file mode 100644 index 000000000..10b4d5fda --- /dev/null +++ b/comphelper/source/property/ChainablePropertySetInfo.cxx @@ -0,0 +1,95 @@ +/* -*- 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/ChainablePropertySetInfo.hxx> +#include <sal/log.hxx> + +using ::comphelper::PropertyInfo; +using ::comphelper::ChainablePropertySetInfo; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::beans::Property; +using ::com::sun::star::beans::UnknownPropertyException; + +ChainablePropertySetInfo::ChainablePropertySetInfo( PropertyInfo const * pMap ) +{ + for( ; !pMap->maName.isEmpty(); ++pMap ) + { + SAL_WARN_IF( + maMap.find(pMap->maName) != maMap.end(), + "comphelper", "Duplicate property name \"" << pMap->maName << "\""); + maMap[pMap->maName] = pMap; + } +} + +ChainablePropertySetInfo::~ChainablePropertySetInfo() + noexcept +{ +} + +void ChainablePropertySetInfo::remove( const OUString& aName ) +{ + maMap.erase ( aName ); + if ( maProperties.hasElements() ) + maProperties.realloc( 0 ); +} + +Sequence< ::Property > SAL_CALL ChainablePropertySetInfo::getProperties() +{ + sal_Int32 nSize = maMap.size(); + if( maProperties.getLength() != nSize ) + { + maProperties.realloc ( nSize ); + Property* pProperties = maProperties.getArray(); + + for (auto const& elem : maMap) + { + PropertyInfo const * pInfo = elem.second; + + pProperties->Name = pInfo->maName; + pProperties->Handle = pInfo->mnHandle; + pProperties->Type = pInfo->maType; + pProperties->Attributes = pInfo->mnAttributes; + ++pProperties; + } + } + return maProperties; +} + +Property SAL_CALL ChainablePropertySetInfo::getPropertyByName( const OUString& rName ) +{ + PropertyInfoHash::iterator aIter = maMap.find( rName ); + + if ( maMap.end() == aIter ) + throw UnknownPropertyException( rName, *this ); + + PropertyInfo const *pInfo = (*aIter).second; + Property aProperty; + aProperty.Name = pInfo->maName; + aProperty.Handle = pInfo->mnHandle; + aProperty.Type = pInfo->maType; + aProperty.Attributes = pInfo->mnAttributes; + return aProperty; +} + +sal_Bool SAL_CALL ChainablePropertySetInfo::hasPropertyByName( const OUString& rName ) +{ + return maMap.find ( rName ) != maMap.end(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/MasterPropertySet.cxx b/comphelper/source/property/MasterPropertySet.cxx new file mode 100644 index 000000000..922a4c1c6 --- /dev/null +++ b/comphelper/source/property/MasterPropertySet.cxx @@ -0,0 +1,398 @@ +/* -*- 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/MasterPropertySet.hxx> +#include <comphelper/MasterPropertySetInfo.hxx> +#include <comphelper/ChainablePropertySet.hxx> +#include <comphelper/ChainablePropertySetInfo.hxx> +#include <comphelper/solarmutex.hxx> + + +#include <memory> +#include <vector> +#include <optional> + +namespace { + +class AutoOGuardArray +{ + std::vector<std::optional< osl::Guard< comphelper::SolarMutex > >> maGuardArray; + +public: + explicit AutoOGuardArray( sal_Int32 nNumElements ); + + std::optional< osl::Guard< comphelper::SolarMutex > > & operator[] ( sal_Int32 i ) { return maGuardArray[i]; } +}; + +} + +AutoOGuardArray::AutoOGuardArray( sal_Int32 nNumElements ) : maGuardArray(nNumElements) +{ +} + + +using namespace ::comphelper; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; + + +SlaveData::SlaveData ( ChainablePropertySet *pSlave) +: mxSlave ( pSlave ) +, mbInit ( false ) +{ +} + +MasterPropertySet::MasterPropertySet( comphelper::MasterPropertySetInfo* pInfo, comphelper::SolarMutex* pMutex ) + noexcept +: mpMutex ( pMutex ) +, mnLastId ( 0 ) +, mxInfo ( pInfo ) +{ +} + +MasterPropertySet::~MasterPropertySet() + noexcept +{ + for( const auto& rSlave : maSlaveMap ) + delete rSlave.second; +} + +// XPropertySet +Reference< XPropertySetInfo > SAL_CALL MasterPropertySet::getPropertySetInfo( ) +{ + return mxInfo; +} + +void MasterPropertySet::registerSlave ( ChainablePropertySet *pNewSet ) + noexcept +{ + maSlaveMap [ ++mnLastId ] = new SlaveData ( pNewSet ); + mxInfo->add ( pNewSet->mxInfo->maMap, mnLastId ); +} + +void SAL_CALL MasterPropertySet::setPropertyValue( const OUString& rPropertyName, const Any& rValue ) +{ + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + std::optional< osl::Guard< comphelper::SolarMutex > > xMutexGuard; + if (mpMutex) + xMutexGuard.emplace( mpMutex ); + + PropertyDataHash::const_iterator aIter = mxInfo->maMap.find ( rPropertyName ); + + if( aIter == mxInfo->maMap.end()) + throw UnknownPropertyException( rPropertyName, static_cast< XPropertySet* >( this ) ); + + if ( (*aIter).second->mnMapId == 0 ) // 0 means it's one of ours ! + { + _preSetValues(); + _setSingleValue( *((*aIter).second->mpInfo), rValue ); + _postSetValues(); + } + else + { + ChainablePropertySet * pSlave = maSlaveMap [ (*aIter).second->mnMapId ]->mxSlave.get(); + + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + std::optional< osl::Guard< comphelper::SolarMutex > > xMutexGuard2; + if (pSlave->mpMutex) + xMutexGuard2.emplace( pSlave->mpMutex ); + + pSlave->_preSetValues(); + pSlave->_setSingleValue( *((*aIter).second->mpInfo), rValue ); + pSlave->_postSetValues(); + } +} + +Any SAL_CALL MasterPropertySet::getPropertyValue( const OUString& rPropertyName ) +{ + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + std::optional< osl::Guard< comphelper::SolarMutex > > xMutexGuard; + if (mpMutex) + xMutexGuard.emplace( mpMutex ); + + PropertyDataHash::const_iterator aIter = mxInfo->maMap.find ( rPropertyName ); + + if( aIter == mxInfo->maMap.end()) + throw UnknownPropertyException( rPropertyName, static_cast< XPropertySet* >( this ) ); + + Any aAny; + if ( (*aIter).second->mnMapId == 0 ) // 0 means it's one of ours ! + { + _preGetValues(); + _getSingleValue( *((*aIter).second->mpInfo), aAny ); + _postGetValues(); + } + else + { + ChainablePropertySet * pSlave = maSlaveMap [ (*aIter).second->mnMapId ]->mxSlave.get(); + + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + std::optional< osl::Guard< comphelper::SolarMutex > > xMutexGuard2; + if (pSlave->mpMutex) + xMutexGuard2.emplace( pSlave->mpMutex ); + + pSlave->_preGetValues(); + pSlave->_getSingleValue( *((*aIter).second->mpInfo), aAny ); + pSlave->_postGetValues(); + } + return aAny; +} + +void SAL_CALL MasterPropertySet::addPropertyChangeListener( const OUString&, const Reference< XPropertyChangeListener >& ) +{ + // todo +} + +void SAL_CALL MasterPropertySet::removePropertyChangeListener( const OUString&, const Reference< XPropertyChangeListener >& ) +{ + // todo +} + +void SAL_CALL MasterPropertySet::addVetoableChangeListener( const OUString&, const Reference< XVetoableChangeListener >& ) +{ + // todo +} + +void SAL_CALL MasterPropertySet::removeVetoableChangeListener( const OUString&, const Reference< XVetoableChangeListener >& ) +{ + // todo +} + +// XMultiPropertySet +void SAL_CALL MasterPropertySet::setPropertyValues( const Sequence< OUString >& aPropertyNames, const Sequence< Any >& aValues ) +{ + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + std::optional< osl::Guard< comphelper::SolarMutex > > xMutexGuard; + if (mpMutex) + xMutexGuard.emplace( mpMutex ); + + const sal_Int32 nCount = aPropertyNames.getLength(); + + if( nCount != aValues.getLength() ) + throw IllegalArgumentException(); + + if( !nCount ) + return; + + _preSetValues(); + + const Any * pAny = aValues.getConstArray(); + const OUString * pString = aPropertyNames.getConstArray(); + PropertyDataHash::const_iterator aEnd = mxInfo->maMap.end(), aIter; + + //!! have a unique_ptr to an array of OGuards in order to have the + //!! allocated memory properly freed (exception safe!). + //!! Since the array itself has unique_ptrs as members we have to use a + //!! helper class 'AutoOGuardArray' in order to have + //!! the acquired locks properly released. + AutoOGuardArray aOGuardArray( nCount ); + + for ( sal_Int32 i = 0; i < nCount; ++i, ++pString, ++pAny ) + { + aIter = mxInfo->maMap.find ( *pString ); + if ( aIter == aEnd ) + throw RuntimeException( *pString, static_cast< XPropertySet* >( this ) ); + + if ( (*aIter).second->mnMapId == 0 ) // 0 means it's one of ours ! + _setSingleValue( *((*aIter).second->mpInfo), *pAny ); + else + { + SlaveData * pSlave = maSlaveMap [ (*aIter).second->mnMapId ]; + if (!pSlave->IsInit()) + { + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + if (pSlave->mxSlave->mpMutex) + aOGuardArray[i].emplace( pSlave->mxSlave->mpMutex ); + + pSlave->mxSlave->_preSetValues(); + pSlave->SetInit ( true ); + } + pSlave->mxSlave->_setSingleValue( *((*aIter).second->mpInfo), *pAny ); + } + } + + _postSetValues(); + for( const auto& rSlave : maSlaveMap ) + { + if( rSlave.second->IsInit() ) + { + rSlave.second->mxSlave->_postSetValues(); + rSlave.second->SetInit( false ); + } + } +} + +Sequence< Any > SAL_CALL MasterPropertySet::getPropertyValues( const Sequence< OUString >& aPropertyNames ) +{ + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + std::optional< osl::Guard< comphelper::SolarMutex > > xMutexGuard; + if (mpMutex) + xMutexGuard.emplace( mpMutex ); + + const sal_Int32 nCount = aPropertyNames.getLength(); + + Sequence < Any > aValues ( nCount ); + + if( nCount ) + { + _preGetValues(); + + Any * pAny = aValues.getArray(); + const OUString * pString = aPropertyNames.getConstArray(); + PropertyDataHash::const_iterator aEnd = mxInfo->maMap.end(), aIter; + + //!! have a unique_ptr to an array of OGuards in order to have the + //!! allocated memory properly freed (exception safe!). + //!! Since the array itself has unique_ptrs as members we have to use a + //!! helper class 'AutoOGuardArray' in order to have + //!! the acquired locks properly released. + AutoOGuardArray aOGuardArray( nCount ); + + for ( sal_Int32 i = 0; i < nCount; ++i, ++pString, ++pAny ) + { + aIter = mxInfo->maMap.find ( *pString ); + if ( aIter == aEnd ) + throw RuntimeException( *pString, static_cast< XPropertySet* >( this ) ); + + if ( (*aIter).second->mnMapId == 0 ) // 0 means it's one of ours ! + _getSingleValue( *((*aIter).second->mpInfo), *pAny ); + else + { + SlaveData * pSlave = maSlaveMap [ (*aIter).second->mnMapId ]; + if (!pSlave->IsInit()) + { + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + if (pSlave->mxSlave->mpMutex) + aOGuardArray[i].emplace( pSlave->mxSlave->mpMutex ); + + pSlave->mxSlave->_preGetValues(); + pSlave->SetInit ( true ); + } + pSlave->mxSlave->_getSingleValue( *((*aIter).second->mpInfo), *pAny ); + } + } + + _postSetValues(); + for( const auto& rSlave : maSlaveMap ) + { + if( rSlave.second->IsInit() ) + { + rSlave.second->mxSlave->_postSetValues(); + rSlave.second->SetInit( false ); + } + } + } + return aValues; +} + +void SAL_CALL MasterPropertySet::addPropertiesChangeListener( const Sequence< OUString >&, const Reference< XPropertiesChangeListener >& ) +{ + // todo +} + +void SAL_CALL MasterPropertySet::removePropertiesChangeListener( const Reference< XPropertiesChangeListener >& ) +{ + // todo +} + +void SAL_CALL MasterPropertySet::firePropertiesChangeEvent( const Sequence< OUString >&, const Reference< XPropertiesChangeListener >& ) +{ + // todo +} + +// XPropertyState +PropertyState SAL_CALL MasterPropertySet::getPropertyState( const OUString& PropertyName ) +{ + PropertyDataHash::const_iterator aIter = mxInfo->maMap.find( PropertyName ); + if( aIter == mxInfo->maMap.end()) + throw UnknownPropertyException( PropertyName, static_cast< XPropertySet* >( this ) ); + + // 0 means it's one of ours ! + if ( (*aIter).second->mnMapId != 0 ) + { + ChainablePropertySet * pSlave = maSlaveMap [ (*aIter).second->mnMapId ]->mxSlave.get(); + + // acquire mutex in c-tor and releases it in the d-tor (exception safe!). + std::optional< osl::Guard< comphelper::SolarMutex > > xMutexGuard; + if (pSlave->mpMutex) + xMutexGuard.emplace( pSlave->mpMutex ); + } + + return PropertyState_AMBIGUOUS_VALUE; +} + +Sequence< PropertyState > SAL_CALL MasterPropertySet::getPropertyStates( const Sequence< OUString >& rPropertyNames ) +{ + const sal_Int32 nCount = rPropertyNames.getLength(); + + Sequence< PropertyState > aStates( nCount ); + if( nCount ) + { + PropertyState * pState = aStates.getArray(); + const OUString * pString = rPropertyNames.getConstArray(); + PropertyDataHash::const_iterator aEnd = mxInfo->maMap.end(), aIter; + + for ( sal_Int32 i = 0; i < nCount; ++i, ++pString, ++pState ) + { + aIter = mxInfo->maMap.find ( *pString ); + if ( aIter == aEnd ) + throw UnknownPropertyException( *pString, static_cast< XPropertySet* >( this ) ); + + // 0 means it's one of ours ! + if ( (*aIter).second->mnMapId != 0 ) + { + SlaveData * pSlave = maSlaveMap [ (*aIter).second->mnMapId ]; + if (!pSlave->IsInit()) + { + pSlave->SetInit ( true ); + } + } + } + for( const auto& rSlave : maSlaveMap ) + { + if( rSlave.second->IsInit() ) + { + rSlave.second->SetInit( false ); + } + } + } + return aStates; +} + +void SAL_CALL MasterPropertySet::setPropertyToDefault( const OUString& rPropertyName ) +{ + PropertyDataHash::const_iterator aIter = mxInfo->maMap.find ( rPropertyName ); + + if( aIter == mxInfo->maMap.end()) + throw UnknownPropertyException( rPropertyName, static_cast< XPropertySet* >( this ) ); +} + +Any SAL_CALL MasterPropertySet::getPropertyDefault( const OUString& rPropertyName ) +{ + PropertyDataHash::const_iterator aIter = mxInfo->maMap.find ( rPropertyName ); + + if( aIter == mxInfo->maMap.end()) + throw UnknownPropertyException( rPropertyName, static_cast< XPropertySet* >( this ) ); + return Any(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/MasterPropertySetInfo.cxx b/comphelper/source/property/MasterPropertySetInfo.cxx new file mode 100644 index 000000000..db8ddb769 --- /dev/null +++ b/comphelper/source/property/MasterPropertySetInfo.cxx @@ -0,0 +1,105 @@ +/* -*- 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/MasterPropertySetInfo.hxx> +#include <sal/log.hxx> + +using ::comphelper::PropertyInfo; +using ::comphelper::MasterPropertySetInfo; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::beans::Property; +using ::com::sun::star::beans::UnknownPropertyException; + +MasterPropertySetInfo::MasterPropertySetInfo( PropertyInfo const * pMap ) +{ + for ( ; !pMap->maName.isEmpty(); ++pMap ) + { + SAL_WARN_IF( + maMap.find(pMap->maName) != maMap.end(), + "comphelper", "Duplicate property name \"" << pMap->maName << "\""); + maMap[pMap->maName] = new PropertyData ( 0, pMap ); + } +} + +MasterPropertySetInfo::~MasterPropertySetInfo() + noexcept +{ + for( const auto& rObj : maMap ) + delete rObj.second; +} + +void MasterPropertySetInfo::add( PropertyInfoHash &rHash, sal_uInt8 nMapId ) +{ + if( maProperties.hasElements() ) + maProperties.realloc( 0 ); + + for( const auto& rObj : rHash ) + { + SAL_WARN_IF( + maMap.find(rObj.first) != maMap.end(), + "comphelper", "Duplicate property name \"" << rObj.first << "\""); + maMap[rObj.first] = new PropertyData ( nMapId, rObj.second ); + } +} + +Sequence< ::Property > SAL_CALL MasterPropertySetInfo::getProperties() +{ + sal_Int32 nSize = maMap.size(); + if( maProperties.getLength() != nSize ) + { + maProperties.realloc ( nSize ); + Property* pProperties = maProperties.getArray(); + + for (auto const& elem : maMap) + { + PropertyInfo const * pInfo = elem.second->mpInfo; + + pProperties->Name = pInfo->maName; + pProperties->Handle = pInfo->mnHandle; + pProperties->Type = pInfo->maType; + pProperties->Attributes = pInfo->mnAttributes; + ++pProperties; + } + } + return maProperties; +} + +Property SAL_CALL MasterPropertySetInfo::getPropertyByName( const OUString& rName ) +{ + PropertyDataHash::iterator aIter = maMap.find( rName ); + + if ( maMap.end() == aIter ) + throw UnknownPropertyException( rName, *this ); + + PropertyInfo const *pInfo = (*aIter).second->mpInfo; + Property aProperty; + aProperty.Name = pInfo->maName; + aProperty.Handle = pInfo->mnHandle; + aProperty.Type = pInfo->maType; + + aProperty.Attributes = pInfo->mnAttributes; + return aProperty; +} + +sal_Bool SAL_CALL MasterPropertySetInfo::hasPropertyByName( const OUString& rName ) +{ + return maMap.find ( rName ) != maMap.end(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/genericpropertyset.cxx b/comphelper/source/property/genericpropertyset.cxx new file mode 100644 index 000000000..b459de64c --- /dev/null +++ b/comphelper/source/property/genericpropertyset.cxx @@ -0,0 +1,247 @@ +/* -*- 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 <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <cppuhelper/weakagg.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/multiinterfacecontainer4.hxx> +#include <comphelper/propertysethelper.hxx> +#include <mutex> +#include <rtl/ref.hxx> +#include <comphelper/genericpropertyset.hxx> +#include <comphelper/propertysetinfo.hxx> + +using namespace ::osl; +using namespace ::cppu; +using namespace ::comphelper; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; + +namespace comphelper +{ + namespace { + + class GenericPropertySet : public OWeakAggObject, + public XServiceInfo, + public XTypeProvider, + public PropertySetHelper + { + private: + std::map<OUString, Any> maAnyMap; + std::mutex maMutex; + comphelper::OMultiTypeInterfaceContainerHelperVar4<OUString, XPropertyChangeListener> m_aListener; + + protected: + virtual void _setPropertyValues( const PropertyMapEntry** ppEntries, const Any* pValues ) override; + virtual void _getPropertyValues( const PropertyMapEntry** ppEntries, Any* pValue ) override; + + public: + explicit GenericPropertySet( PropertySetInfo* pInfo ) noexcept; + + // XInterface + virtual Any SAL_CALL queryAggregation( const Type & rType ) override; + virtual Any SAL_CALL queryInterface( const Type & rType ) override; + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + + // XTypeProvider + virtual Sequence< Type > SAL_CALL getTypes( ) override; + virtual Sequence< sal_Int8 > SAL_CALL getImplementationId( ) 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; + + // XPropertySet + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + }; + + } +} + + +GenericPropertySet::GenericPropertySet( PropertySetInfo* pInfo ) noexcept +: PropertySetHelper( pInfo ) +{ +} + +void SAL_CALL GenericPropertySet::addPropertyChangeListener( const OUString& aPropertyName, const Reference< XPropertyChangeListener >& xListener ) +{ + Reference < XPropertySetInfo > xInfo = getPropertySetInfo( ); + if ( !xInfo.is() ) + return; + + std::unique_lock aGuard(maMutex); + if ( aPropertyName.isEmpty() ) + { + Sequence< Property> aSeq = xInfo->getProperties(); + const Property* pIter = aSeq.getConstArray(); + const Property* pEnd = pIter + aSeq.getLength(); + for( ; pIter != pEnd ; ++pIter) + { + m_aListener.addInterface(aGuard, pIter->Name,xListener); + } + } + else if ( xInfo->hasPropertyByName(aPropertyName) ) + m_aListener.addInterface(aGuard, aPropertyName,xListener); + else + throw UnknownPropertyException( aPropertyName, *this ); +} + +void SAL_CALL GenericPropertySet::removePropertyChangeListener( const OUString& aPropertyName, const Reference< XPropertyChangeListener >& xListener ) +{ + Reference < XPropertySetInfo > xInfo = getPropertySetInfo( ); + if ( !xInfo.is() ) + return; + + std::unique_lock aGuard(maMutex); + if ( aPropertyName.isEmpty() ) + { + Sequence< Property> aSeq = xInfo->getProperties(); + const Property* pIter = aSeq.getConstArray(); + const Property* pEnd = pIter + aSeq.getLength(); + for( ; pIter != pEnd ; ++pIter) + { + m_aListener.removeInterface(aGuard, pIter->Name,xListener); + } + } + else if ( xInfo->hasPropertyByName(aPropertyName) ) + m_aListener.removeInterface(aGuard, aPropertyName,xListener); + else + throw UnknownPropertyException( aPropertyName, *this ); +} + +void GenericPropertySet::_setPropertyValues( const PropertyMapEntry** ppEntries, const Any* pValues ) +{ + std::unique_lock aGuard(maMutex); + + while( *ppEntries ) + { + OInterfaceContainerHelper4<XPropertyChangeListener> * pHelper = m_aListener.getContainer((*ppEntries)->maName); + + maAnyMap[ (*ppEntries)->maName ] = *pValues; + + if ( pHelper ) + { + PropertyChangeEvent aEvt; + aEvt.PropertyName = (*ppEntries)->maName; + aEvt.NewValue = *pValues; + pHelper->notifyEach( aGuard, &XPropertyChangeListener::propertyChange, aEvt ); + aGuard.lock(); + } + + ppEntries++; + pValues++; + } +} + +void GenericPropertySet::_getPropertyValues( const comphelper::PropertyMapEntry** ppEntries, Any* pValue ) +{ + std::unique_lock aGuard(maMutex); + + while( *ppEntries ) + { + *pValue = maAnyMap[ (*ppEntries)->maName ]; + + ppEntries++; + pValue++; + } +} + +// XInterface + +Any SAL_CALL GenericPropertySet::queryInterface( const Type & rType ) +{ + return OWeakAggObject::queryInterface( rType ); +} + +Any SAL_CALL GenericPropertySet::queryAggregation( const Type & rType ) +{ + Any aAny; + + if( rType == cppu::UnoType<XServiceInfo>::get()) + aAny <<= Reference< XServiceInfo >(this); + else if( rType == cppu::UnoType<XTypeProvider>::get()) + aAny <<= Reference< XTypeProvider >(this); + else if( rType == cppu::UnoType<XPropertySet>::get()) + aAny <<= Reference< XPropertySet >(this); + else if( rType == cppu::UnoType<XMultiPropertySet>::get()) + aAny <<= Reference< XMultiPropertySet >(this); + else + aAny = OWeakAggObject::queryAggregation( rType ); + + return aAny; +} + +void SAL_CALL GenericPropertySet::acquire() noexcept +{ + OWeakAggObject::acquire(); +} + +void SAL_CALL GenericPropertySet::release() noexcept +{ + OWeakAggObject::release(); +} + +uno::Sequence< uno::Type > SAL_CALL GenericPropertySet::getTypes() +{ + return uno::Sequence { + cppu::UnoType<XAggregation>::get(), + cppu::UnoType<XServiceInfo>::get(), + cppu::UnoType<XTypeProvider>::get(), + cppu::UnoType<XPropertySet>::get(), + cppu::UnoType<XMultiPropertySet>::get() }; +} + +uno::Sequence< sal_Int8 > SAL_CALL GenericPropertySet::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XServiceInfo +sal_Bool SAL_CALL GenericPropertySet::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +OUString SAL_CALL GenericPropertySet::getImplementationName() +{ + return "com.sun.star.comp.comphelper.GenericPropertySet"; +} + +Sequence< OUString > SAL_CALL GenericPropertySet::getSupportedServiceNames( ) +{ + return { "com.sun.star.beans.XPropertySet" }; +} + +css::uno::Reference< css::beans::XPropertySet > comphelper::GenericPropertySet_CreateInstance( comphelper::PropertySetInfo* pInfo ) +{ + return static_cast<XPropertySet*>(new GenericPropertySet( pInfo )); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/opropertybag.cxx b/comphelper/source/property/opropertybag.cxx new file mode 100644 index 000000000..e0b389c19 --- /dev/null +++ b/comphelper/source/property/opropertybag.cxx @@ -0,0 +1,540 @@ +/* -*- 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 "opropertybag.hxx" + +#include <com/sun/star/beans/IllegalTypeException.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/Property.hpp> + +#include <comphelper/namedvaluecollection.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <cppuhelper/exc_hlp.hxx> + +#include <algorithm> + +namespace com::sun::star::uno { class XComponentContext; } + +using namespace ::com::sun::star; + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_comphelper_OPropertyBag ( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new comphelper::OPropertyBag()); +} + +namespace comphelper +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::container; + + OPropertyBag::OPropertyBag() + :OPropertyBag_PBase( GetBroadcastHelper(), this ) + ,::cppu::IEventNotificationHook() + ,m_bAutoAddProperties( false ) + ,m_NotifyListeners(m_aMutex) + ,m_isModified(false) + + { + } + + + OPropertyBag::~OPropertyBag() + { + } + + + IMPLEMENT_FORWARD_XINTERFACE2( OPropertyBag, OPropertyBag_Base, OPropertyBag_PBase ) + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OPropertyBag, OPropertyBag_Base, OPropertyBag_PBase ) + + void SAL_CALL OPropertyBag::initialize( const Sequence< Any >& _rArguments ) + { + Sequence< Type > aTypes; + bool AllowEmptyPropertyName(false); + bool AutomaticAddition(false); + + if (_rArguments.getLength() == 3 + && (_rArguments[0] >>= aTypes) + && (_rArguments[1] >>= AllowEmptyPropertyName) + && (_rArguments[2] >>= AutomaticAddition)) + { + m_aAllowedTypes.insert(std::cbegin(aTypes), std::cend(aTypes)); + m_bAutoAddProperties = AutomaticAddition; + + } else { + ::comphelper::NamedValueCollection aArguments( _rArguments ); + + if ( aArguments.get_ensureType( "AllowedTypes", aTypes ) ) + m_aAllowedTypes.insert(std::cbegin(aTypes), std::cend(aTypes)); + + aArguments.get_ensureType( "AutomaticAddition", m_bAutoAddProperties ); + aArguments.get_ensureType( "AllowEmptyPropertyName", + AllowEmptyPropertyName ); + } + if (AllowEmptyPropertyName) { + m_aDynamicProperties.setAllowEmptyPropertyName( + AllowEmptyPropertyName); + } + } + + OUString SAL_CALL OPropertyBag::getImplementationName() + { + return "com.sun.star.comp.comphelper.OPropertyBag"; + } + + sal_Bool SAL_CALL OPropertyBag::supportsService( const OUString& rServiceName ) + { + return cppu::supportsService(this, rServiceName); + } + + Sequence< OUString > SAL_CALL OPropertyBag::getSupportedServiceNames( ) + { + return { "com.sun.star.beans.PropertyBag" }; + } + + void OPropertyBag::fireEvents( + sal_Int32 * /*pnHandles*/, + sal_Int32 nCount, + sal_Bool bVetoable, + bool bIgnoreRuntimeExceptionsWhileFiring) + { + if (nCount && !bVetoable) { + setModifiedImpl(true, bIgnoreRuntimeExceptionsWhileFiring); + } + } + + void OPropertyBag::setModifiedImpl(bool bModified, + bool bIgnoreRuntimeExceptionsWhileFiring) + { + { // do not lock mutex while notifying (#i93514#) to prevent deadlock + ::osl::MutexGuard aGuard( m_aMutex ); + m_isModified = bModified; + } + if (!bModified) + return; + + try { + Reference<XInterface> xThis(*this); + EventObject event(xThis); + m_NotifyListeners.notifyEach( + &XModifyListener::modified, event); + } catch (RuntimeException &) { + if (!bIgnoreRuntimeExceptionsWhileFiring) { + throw; + } + } catch (Exception &) { + // ignore + } + } + + + sal_Bool SAL_CALL OPropertyBag::isModified() + { + ::osl::MutexGuard aGuard( m_aMutex ); + return m_isModified; + } + + void SAL_CALL OPropertyBag::setModified( sal_Bool bModified ) + { + setModifiedImpl(bModified, false); + } + + void SAL_CALL OPropertyBag::addModifyListener( + const Reference< XModifyListener > & xListener) + { + m_NotifyListeners.addInterface(xListener); + } + + void SAL_CALL OPropertyBag::removeModifyListener( + const Reference< XModifyListener > & xListener) + { + m_NotifyListeners.removeInterface(xListener); + } + + + Reference< XPropertySetInfo > SAL_CALL OPropertyBag::getPropertySetInfo( ) + { + return createPropertySetInfo( getInfoHelper() ); + } + + + sal_Bool SAL_CALL OPropertyBag::has( const Any& /*aElement*/ ) + { + // XSet is only a workaround for addProperty not being able to add default-void properties. + // So, everything of XSet except insert is implemented empty + return false; + } + + + void SAL_CALL OPropertyBag::insert( const Any& _element ) + { + // This is a workaround for addProperty not being able to add default-void properties. + // If we ever have a smarter XPropertyContainer::addProperty interface, we can remove this, ehm, well, hack. + Property aProperty; + if ( !( _element >>= aProperty ) ) + throw IllegalArgumentException( "element is not Property", *this, 1 ); + + { + osl::MutexGuard g(m_aMutex); + + // check whether the type is allowed, everything else will be checked + // by m_aDynamicProperties + if (!m_aAllowedTypes.empty() + && m_aAllowedTypes.find(aProperty.Type) == m_aAllowedTypes.end()) + throw IllegalArgumentException("not in list of allowed types", *this, 1); + + m_aDynamicProperties.addVoidProperty(aProperty.Name, aProperty.Type, findFreeHandle(), + aProperty.Attributes); + + // our property info is dirty + m_pArrayHelper.reset(); + } + setModified(true); + } + + + void SAL_CALL OPropertyBag::remove( const Any& /*aElement*/ ) + { + // XSet is only a workaround for addProperty not being able to add default-void properties. + // So, everything of XSet except insert is implemented empty + throw NoSuchElementException( OUString(), *this ); + } + + + Reference< XEnumeration > SAL_CALL OPropertyBag::createEnumeration( ) + { + // XSet is only a workaround for addProperty not being able to add default-void properties. + // So, everything of XSet except insert is implemented empty + return nullptr; + } + + + Type SAL_CALL OPropertyBag::getElementType( ) + { + // XSet is only a workaround for addProperty not being able to add default-void properties. + // So, everything of XSet except insert is implemented empty + return Type(); + } + + + sal_Bool SAL_CALL OPropertyBag::hasElements( ) + { + // XSet is only a workaround for addProperty not being able to add default-void properties. + // So, everything of XSet except insert is implemented empty + return false; + } + + + void SAL_CALL OPropertyBag::getFastPropertyValue( Any& _rValue, sal_Int32 _nHandle ) const + { + m_aDynamicProperties.getFastPropertyValue( _nHandle, _rValue ); + } + + sal_Bool SAL_CALL OPropertyBag::convertFastPropertyValue( Any& _rConvertedValue, Any& _rOldValue, sal_Int32 _nHandle, const Any& _rValue ) + { + return m_aDynamicProperties.convertFastPropertyValue( _nHandle, _rValue, _rConvertedValue, _rOldValue ); + } + + void SAL_CALL OPropertyBag::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, const Any& rValue ) + { + m_aDynamicProperties.setFastPropertyValue( nHandle, rValue ); + } + + + ::cppu::IPropertyArrayHelper& SAL_CALL OPropertyBag::getInfoHelper() + { + if (!m_pArrayHelper) + { + Sequence< Property > aProperties; + m_aDynamicProperties.describeProperties( aProperties ); + m_pArrayHelper.reset( new ::cppu::OPropertyArrayHelper( aProperties ) ); + } + return *m_pArrayHelper; + + } + + + sal_Int32 OPropertyBag::findFreeHandle() const + { + const sal_Int32 nPrime = 1009; + const sal_Int32 nSeed = 11; + + sal_Int32 nCheck = nSeed; + while ( m_aDynamicProperties.hasPropertyByHandle( nCheck ) && ( nCheck != 1 ) ) + { + nCheck = ( nCheck * nSeed ) % nPrime; + } + + if ( nCheck == 1 ) + { // uh ... we already have 1008 handles used up + // -> simply count upwards + while ( m_aDynamicProperties.hasPropertyByHandle( nCheck ) ) + ++nCheck; + } + + return nCheck; + } + + + void SAL_CALL OPropertyBag::addProperty( const OUString& _rName, ::sal_Int16 _nAttributes, const Any& _rInitialValue ) + { + { + osl::MutexGuard g(m_aMutex); + + // check whether the type is allowed, everything else will be checked + // by m_aDynamicProperties + const Type& aPropertyType = _rInitialValue.getValueType(); + if (_rInitialValue.hasValue() && !m_aAllowedTypes.empty() + && m_aAllowedTypes.find(aPropertyType) == m_aAllowedTypes.end()) + throw IllegalTypeException(OUString(), *this); + + m_aDynamicProperties.addProperty(_rName, findFreeHandle(), _nAttributes, + _rInitialValue); + + // our property info is dirty + m_pArrayHelper.reset(); + } + setModified(true); + } + + + void SAL_CALL OPropertyBag::removeProperty( const OUString& _rName ) + { + { + osl::MutexGuard g(m_aMutex); + + m_aDynamicProperties.removeProperty(_rName); + + // our property info is dirty + m_pArrayHelper.reset(); + } + setModified(true); + } + + + namespace + { + struct ComparePropertyValueByName + { + bool operator()( const PropertyValue& _rLHS, const PropertyValue& _rRHS ) + { + return _rLHS.Name < _rRHS.Name; + } + }; + + template< typename CLASS > + struct TransformPropertyToName + { + const OUString& operator()( const CLASS& _rProp ) + { + return _rProp.Name; + } + }; + + struct ExtractPropertyValue + { + const Any& operator()( const PropertyValue& _rProp ) + { + return _rProp.Value; + } + }; + } + + + Sequence< PropertyValue > SAL_CALL OPropertyBag::getPropertyValues( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // all registered properties + Sequence< Property > aProperties; + m_aDynamicProperties.describeProperties( aProperties ); + + // their names + Sequence< OUString > aNames( aProperties.getLength() ); + std::transform( + std::cbegin(aProperties), + std::cend(aProperties), + aNames.getArray(), + TransformPropertyToName< Property >() + ); + + // their values + Sequence< Any > aValues; + try + { + aValues = OPropertyBag_PBase::getPropertyValues( aNames ); + if ( aValues.getLength() != aNames.getLength() ) + throw RuntimeException(); + } + catch( const RuntimeException& ) + { + throw; + } + catch( const Exception& ) + { + // ignore + } + + // merge names and values, and retrieve the state/handle + ::cppu::IPropertyArrayHelper& rPropInfo = getInfoHelper(); + + Sequence< PropertyValue > aPropertyValues( aNames.getLength() ); + const OUString* pName = aNames.getConstArray(); + const OUString* pNamesEnd = aNames.getConstArray() + aNames.getLength(); + const Any* pValue = aValues.getArray(); + PropertyValue* pPropertyValue = aPropertyValues.getArray(); + + for ( ; pName != pNamesEnd; ++pName, ++pValue, ++pPropertyValue ) + { + pPropertyValue->Name = *pName; + pPropertyValue->Handle = rPropInfo.getHandleByName( *pName ); + pPropertyValue->Value = *pValue; + pPropertyValue->State = getPropertyStateByHandle( pPropertyValue->Handle ); + } + + return aPropertyValues; + } + + + void OPropertyBag::impl_setPropertyValues_throw( const Sequence< PropertyValue >& _rProps ) + { + // sort (the XMultiPropertySet interface requires this) + Sequence< PropertyValue > aProperties( _rProps ); + auto [begin, end] = asNonConstRange(aProperties); + std::sort( + begin, + end, + ComparePropertyValueByName() + ); + + // a sequence of names + Sequence< OUString > aNames( aProperties.getLength() ); + std::transform( + std::cbegin(aProperties), + std::cend(aProperties), + aNames.getArray(), + TransformPropertyToName< PropertyValue >() + ); + + try + { + // check for unknown properties + // we cannot simply rely on the XMultiPropertySet::setPropertyValues + // implementation of our base class, since it does not throw + // an UnknownPropertyException. More precise, XMultiPropertySet::setPropertyValues + // does not allow to throw this exception, while XPropertyAccess::setPropertyValues + // requires it + sal_Int32 nCount = aNames.getLength(); + + Sequence< sal_Int32 > aHandles( nCount ); + sal_Int32* pHandle = aHandles.getArray(); + const PropertyValue* pProperty = aProperties.getConstArray(); + for ( const OUString* pName = aNames.getConstArray(); + pName != aNames.getConstArray() + aNames.getLength(); + ++pName, ++pHandle, ++pProperty + ) + { + ::cppu::IPropertyArrayHelper& rPropInfo = getInfoHelper(); + *pHandle = rPropInfo.getHandleByName( *pName ); + if ( *pHandle != -1 ) + continue; + + // there's a property requested which we do not know + if ( m_bAutoAddProperties ) + { + // add the property + sal_Int16 const nAttributes = PropertyAttribute::BOUND | PropertyAttribute::REMOVABLE | PropertyAttribute::MAYBEDEFAULT; + addProperty( *pName, nAttributes, pProperty->Value ); + continue; + } + + // no way out + throw UnknownPropertyException( *pName, *this ); + } + + // a sequence of values + Sequence< Any > aValues( aProperties.getLength() ); + std::transform( + std::cbegin(aProperties), + std::cend(aProperties), + aValues.getArray(), + ExtractPropertyValue() + ); + + setFastPropertyValues( nCount, aHandles.getArray(), aValues.getConstArray(), nCount ); + } + catch( const PropertyVetoException& ) { throw; } + catch( const IllegalArgumentException& ) { throw; } + catch( const WrappedTargetException& ) { throw; } + catch( const RuntimeException& ) { throw; } + catch( const UnknownPropertyException& ) { throw; } + catch( const Exception& ) + { + throw WrappedTargetException( OUString(), *this, ::cppu::getCaughtException() ); + } + } + + + void SAL_CALL OPropertyBag::setPropertyValues( const Sequence< PropertyValue >& _rProps ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + impl_setPropertyValues_throw( _rProps ); + } + + + PropertyState OPropertyBag::getPropertyStateByHandle( sal_Int32 _nHandle ) + { + // for properties which do not support the MAYBEDEFAULT attribute, don't rely on the base class, but + // assume they're always in DIRECT state. + // (Note that this probably would belong into the base class. However, this would mean we would need + // to check all existent usages of the base class, where MAYBEDEFAULT is *not* set, but + // a default is nonetheless supplied/used. This is hard to accomplish reliably, in the + // current phase. #i78593# + + ::cppu::IPropertyArrayHelper& rPropInfo = getInfoHelper(); + sal_Int16 nAttributes(0); + OSL_VERIFY( rPropInfo.fillPropertyMembersByHandle( nullptr, &nAttributes, _nHandle ) ); + if ( ( nAttributes & PropertyAttribute::MAYBEDEFAULT ) == 0 ) + return PropertyState_DIRECT_VALUE; + + return OPropertyBag_PBase::getPropertyStateByHandle( _nHandle ); + } + + + Any OPropertyBag::getPropertyDefaultByHandle( sal_Int32 _nHandle ) const + { + Any aDefault; + m_aDynamicProperties.getPropertyDefaultByHandle( _nHandle, aDefault ); + return aDefault; + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/opropertybag.hxx b/comphelper/source/property/opropertybag.hxx new file mode 100644 index 000000000..28333901c --- /dev/null +++ b/comphelper/source/property/opropertybag.hxx @@ -0,0 +1,218 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/util/XModifiable.hpp> +#include <com/sun/star/beans/XPropertyBag.hpp> +#include <com/sun/star/container/XSet.hpp> + +#include <cppuhelper/implbase5.hxx> +#include <comphelper/interfacecontainer3.hxx> +#include <comphelper/propstate.hxx> +#include <comphelper/broadcasthelper.hxx> +#include <comphelper/propertybag.hxx> +#include <comphelper/uno3.hxx> + +#include <map> +#include <set> +#include <memory> + + +namespace comphelper +{ + + + struct UnoTypeLess + { + bool operator()( const css::uno::Type& _rLHS, const css::uno::Type& _rRHS ) const + { + return rtl_ustr_compare( + _rLHS.getTypeLibType()->pTypeName->buffer, + _rRHS.getTypeLibType()->pTypeName->buffer + ) < 0; + } + }; + + typedef std::map< sal_Int32, css::uno::Any > MapInt2Any; + typedef std::set< css::uno::Type, UnoTypeLess > TypeBag; + + typedef ::cppu::WeakAggImplHelper5 < css::beans::XPropertyBag + , css::util::XModifiable + , css::lang::XServiceInfo + , css::lang::XInitialization + , css::container::XSet + > OPropertyBag_Base; + typedef ::comphelper::OPropertyStateHelper OPropertyBag_PBase; + + class OPropertyBag final : public ::comphelper::OMutexAndBroadcastHelper // must be before OPropertyBag_PBase + ,public OPropertyBag_PBase + ,public OPropertyBag_Base + ,public ::cppu::IEventNotificationHook + { + private: + /// our IPropertyArrayHelper implementation + std::unique_ptr< ::cppu::OPropertyArrayHelper > + m_pArrayHelper; + ::comphelper::PropertyBag + m_aDynamicProperties; + /// set of allowed property types + TypeBag m_aAllowedTypes; + /// should we automatically add properties which are tried to set, if they don't exist previously? + bool m_bAutoAddProperties; + + /// for notification + ::comphelper::OInterfaceContainerHelper3<css::util::XModifyListener> m_NotifyListeners; + /// modify flag + bool m_isModified; + + public: + //noncopyable + OPropertyBag(const OPropertyBag&) = delete; + const OPropertyBag& operator=(const OPropertyBag&) = delete; + OPropertyBag(); + virtual ~OPropertyBag() override; + + private: + DECLARE_XINTERFACE() + DECLARE_XTYPEPROVIDER() + + /** === begin UNO interface implementations == **/ + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) 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; + + // XModifiable: + virtual sal_Bool SAL_CALL isModified( ) override; + virtual void SAL_CALL setModified( sal_Bool bModified ) override; + + // XModifyBroadcaster + virtual void SAL_CALL addModifyListener( + const css::uno::Reference< + css::util::XModifyListener > & xListener) override; + virtual void SAL_CALL removeModifyListener( + const css::uno::Reference< + css::util::XModifyListener > & xListener) override; + + // XPropertyContainer + virtual void SAL_CALL addProperty( const OUString& Name, ::sal_Int16 Attributes, const css::uno::Any& DefaultValue ) override; + virtual void SAL_CALL removeProperty( const OUString& Name ) override; + + // XPropertyAccess + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getPropertyValues( ) override; + virtual void SAL_CALL setPropertyValues( const css::uno::Sequence< css::beans::PropertyValue >& aProps ) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + virtual void SAL_CALL setPropertyValue(const OUString& p1, const css::uno::Any& p2) override + { OPropertyBag_PBase::setPropertyValue(p1, p2); } + virtual css::uno::Any SAL_CALL getPropertyValue(const OUString& p1) override + { return OPropertyBag_PBase::getPropertyValue(p1); } + virtual void SAL_CALL addPropertyChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XPropertyChangeListener>& p2) override + { OPropertyBag_PBase::addPropertyChangeListener(p1, p2); } + virtual void SAL_CALL removePropertyChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XPropertyChangeListener>& p2) override + { OPropertyBag_PBase::removePropertyChangeListener(p1, p2); } + virtual void SAL_CALL addVetoableChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XVetoableChangeListener>& p2) override + { OPropertyBag_PBase::addVetoableChangeListener(p1, p2); } + virtual void SAL_CALL removeVetoableChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XVetoableChangeListener>& p2) override + { OPropertyBag_PBase::removeVetoableChangeListener(p1, p2); } + + // XSet + virtual sal_Bool SAL_CALL has( const css::uno::Any& aElement ) override; + virtual void SAL_CALL insert( const css::uno::Any& aElement ) override; + virtual void SAL_CALL remove( const css::uno::Any& aElement ) override; + + // XEnumerationAccess (base of XSet) + virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createEnumeration( ) override; + + // XElementAccess (base of XEnumerationAccess) + virtual css::uno::Type SAL_CALL getElementType( ) override; + virtual sal_Bool SAL_CALL hasElements( ) override; + // UNO interface implementations + + // XPropertyState + virtual css::uno::Any getPropertyDefaultByHandle( sal_Int32 _nHandle ) const override; + + // OPropertyStateHelper + virtual css::beans::PropertyState getPropertyStateByHandle( sal_Int32 _nHandle ) override; + + // OPropertySetHelper + virtual void SAL_CALL getFastPropertyValue( css::uno::Any& rValue, sal_Int32 nHandle ) const override; + virtual sal_Bool SAL_CALL convertFastPropertyValue( css::uno::Any & rConvertedValue, css::uno::Any & rOldValue, sal_Int32 nHandle, const css::uno::Any& rValue ) override; + virtual void SAL_CALL setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, const css::uno::Any& rValue ) override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + + // IEventNotificationHook + virtual void fireEvents( + sal_Int32 * pnHandles, + sal_Int32 nCount, + sal_Bool bVetoable, + bool bIgnoreRuntimeExceptionsWhileFiring) override; + + void setModifiedImpl( bool bModified, + bool bIgnoreRuntimeExceptionsWhileFiring); + + /** finds a free property handle + @precond + our mutex is locked + */ + sal_Int32 findFreeHandle() const; + + /** implements the setPropertyValues method + @param _rProps + the property values to set + + @throws PropertyVetoException + if the XMultiPropertySet::setPropertyValues call does so + + @throws css::lang::IllegalArgumentException + if the XMultiPropertySet::setPropertyValues call does so + + @throws css::lang::WrappedTargetException + if the XMultiPropertySet::setPropertyValues call does so + + @throws css::uno::RuntimeException + if the XMultiPropertySet::setPropertyValues call does so + + @throws css::beans::UnknownPropertyException + if the XMultiPropertySet::setPropertyValues call does so, and <arg>_bTolerateUnknownProperties</arg> + was set to <FALSE/> + + @throws css::lang::WrappedTargetException + if the XMultiPropertySet::setPropertyValues call did throw an exception not listed + above + */ + void impl_setPropertyValues_throw( const css::uno::Sequence< css::beans::PropertyValue >& _rProps ); + + using ::cppu::OPropertySetHelper::getPropertyValues; + using ::cppu::OPropertySetHelper::setPropertyValues; + using ::cppu::OPropertySetHelper::getFastPropertyValue; + }; + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/propagg.cxx b/comphelper/source/property/propagg.cxx new file mode 100644 index 000000000..0d8ea6a10 --- /dev/null +++ b/comphelper/source/property/propagg.cxx @@ -0,0 +1,870 @@ +/* -*- 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/propagg.hxx> +#include <comphelper/property.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <o3tl/sorted_vector.hxx> +#include <typeinfo> +#include <algorithm> +#include <cstddef> +#include <unordered_set> +#include <memory> + + +namespace comphelper +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + + using namespace internal; + + + namespace + { + const Property* lcl_findPropertyByName( const std::vector< Property >& _rProps, const OUString& _rName ) + { + Property aNameProp(_rName, 0, Type(), 0); + auto pResult = std::lower_bound(_rProps.begin(), _rProps.end(), aNameProp, PropertyCompareByName()); + if ( pResult == _rProps.end() || pResult->Name != _rName ) + return nullptr; + + return &*pResult; + } + } + +OPropertyArrayAggregationHelper::OPropertyArrayAggregationHelper( + const Sequence< Property >& _rProperties, const Sequence< Property >& _rAggProperties, + IPropertyInfoService* _pInfoService, sal_Int32 _nFirstAggregateId ) +{ + // if properties are present both at the delegatee and the aggregate, then the former are supposed to win + // merge and sort properties by name, delete duplicates (stable sort ensures delegator properties win) + m_aProperties.insert( m_aProperties.end(), _rProperties.begin(), _rProperties.end() ); + m_aProperties.insert( m_aProperties.end(), _rAggProperties.begin(), _rAggProperties.end() ); + std::stable_sort( m_aProperties.begin(), m_aProperties.end(), PropertyCompareByName() ); + m_aProperties.erase( std::unique(m_aProperties.begin(), m_aProperties.end(), + []( const css::beans::Property& x, const css::beans::Property& y ) -> bool { return x.Name == y.Name; } ), + m_aProperties.end() ); + m_aProperties.shrink_to_fit(); + + // fill aDelegatorProps with names from _rProperties for a fast existence check + // different kinds of properties are processed differently + std::unordered_set< OUString > aDelegatorProps; + aDelegatorProps.reserve( _rProperties.getLength() ); + for( auto &delegateProp: _rProperties ) + { + const auto inserted = aDelegatorProps.insert( delegateProp.Name ); + OSL_ENSURE( inserted.second, + "OPropertyArrayAggregationHelper::OPropertyArrayAggregationHelper: duplicate delegatee property!" ); + } + + std::unordered_set< sal_Int32 > existingHandles; + existingHandles.reserve( m_aProperties.size() ); + sal_Int32 nAggregateHandle = _nFirstAggregateId; + for ( std::size_t nMPLoop = 0; nMPLoop < m_aProperties.size(); ++nMPLoop ) + { + auto &prop = m_aProperties[ nMPLoop ]; + if ( aDelegatorProps.find( prop.Name ) != aDelegatorProps.end() ) + { + m_aPropertyAccessors.insert_or_assign( + prop.Handle, OPropertyAccessor( -1, nMPLoop, false )); + existingHandles.insert( prop.Handle ); + } + else + { + // determine the handle for the property which we will expose to the outside world + sal_Int32 nHandle = -1; + // ask the info service first + if ( _pInfoService ) + nHandle = _pInfoService->getPreferredPropertyId( prop.Name ); + + if ( ( -1 == nHandle ) || ( existingHandles.find( nHandle ) != existingHandles.end() ) ) + { + // 1. no handle from the info service -> default + // 2. conflicts -> use another one (which we don't check anymore, assuming _nFirstAggregateId was large enough) + nHandle = nAggregateHandle++; + } + else + { + existingHandles.insert( nHandle ); + } + + // remember the accessor for this property + m_aPropertyAccessors.insert_or_assign( + nHandle, OPropertyAccessor( prop.Handle, nMPLoop, true )); + prop.Handle = nHandle; + } + } +} + + +OPropertyArrayAggregationHelper::PropertyOrigin OPropertyArrayAggregationHelper::classifyProperty( const OUString& _rName ) +{ + PropertyOrigin eOrigin = PropertyOrigin::Unknown; + // look up the name + const Property* pPropertyDescriptor = lcl_findPropertyByName( m_aProperties, _rName ); + if ( pPropertyDescriptor ) + { + // look up the handle for this name + auto aPos = m_aPropertyAccessors.find( pPropertyDescriptor->Handle ); + OSL_ENSURE( m_aPropertyAccessors.end() != aPos, "OPropertyArrayAggregationHelper::classifyProperty: should have this handle in my map!" ); + if ( m_aPropertyAccessors.end() != aPos ) + { + eOrigin = aPos->second.bAggregate ? PropertyOrigin::Aggregate : PropertyOrigin::Delegator; + } + } + return eOrigin; +} + + +Property OPropertyArrayAggregationHelper::getPropertyByName( const OUString& _rPropertyName ) +{ + const Property* pProperty = findPropertyByName( _rPropertyName ); + + if ( !pProperty ) + throw UnknownPropertyException(_rPropertyName); + + return *pProperty; +} + + +sal_Bool OPropertyArrayAggregationHelper::hasPropertyByName(const OUString& _rPropertyName) +{ + return nullptr != findPropertyByName( _rPropertyName ); +} + + +const Property* OPropertyArrayAggregationHelper::findPropertyByName(const OUString& _rName ) const +{ + return lcl_findPropertyByName( m_aProperties, _rName ); +} + + +sal_Int32 OPropertyArrayAggregationHelper::getHandleByName(const OUString& _rPropertyName) +{ + const Property* pProperty = findPropertyByName( _rPropertyName ); + return pProperty ? pProperty->Handle : -1; +} + + +sal_Bool OPropertyArrayAggregationHelper::fillPropertyMembersByHandle( + OUString* _pPropName, sal_Int16* _pAttributes, sal_Int32 _nHandle) +{ + auto i = m_aPropertyAccessors.find(_nHandle); + bool bRet = i != m_aPropertyAccessors.end(); + if (bRet) + { + const css::beans::Property& rProperty = m_aProperties[(*i).second.nPos]; + if (_pPropName) + *_pPropName = rProperty.Name; + if (_pAttributes) + *_pAttributes = rProperty.Attributes; + } + return bRet; +} + + +bool OPropertyArrayAggregationHelper::getPropertyByHandle( sal_Int32 _nHandle, Property& _rProperty ) const +{ + auto pos = m_aPropertyAccessors.find(_nHandle); + if ( pos != m_aPropertyAccessors.end() ) + { + _rProperty = m_aProperties[ pos->second.nPos ]; + return true; + } + return false; +} + + +bool OPropertyArrayAggregationHelper::fillAggregatePropertyInfoByHandle( + OUString* _pPropName, sal_Int32* _pOriginalHandle, sal_Int32 _nHandle) const +{ + auto i = m_aPropertyAccessors.find(_nHandle); + bool bRet = i != m_aPropertyAccessors.end() && (*i).second.bAggregate; + if (bRet) + { + if (_pOriginalHandle) + *_pOriginalHandle = (*i).second.nOriginalHandle; + if (_pPropName) + { + OSL_ENSURE((*i).second.nPos < m_aProperties.size(),"Invalid index for sequence!"); + const css::beans::Property& rProperty = m_aProperties[(*i).second.nPos]; + *_pPropName = rProperty.Name; + } + } + return bRet; +} + + +css::uno::Sequence< css::beans::Property> OPropertyArrayAggregationHelper::getProperties() +{ + return comphelper::containerToSequence(m_aProperties); +} + + +sal_Int32 OPropertyArrayAggregationHelper::fillHandles( + sal_Int32* _pHandles, const css::uno::Sequence< OUString >& _rPropNames ) +{ + sal_Int32 nHitCount = 0; + const OUString* pReqProps = _rPropNames.getConstArray(); + sal_Int32 nReqLen = _rPropNames.getLength(); + + Property aNameProp; + for( sal_Int32 i = 0; i < nReqLen; ++i ) + { + aNameProp.Name = pReqProps[i]; + auto findIter = std::lower_bound(m_aProperties.begin(), m_aProperties.end(), aNameProp, PropertyCompareByName()); + if ( findIter != m_aProperties.end() && findIter->Name == pReqProps[i] ) + { + _pHandles[i] = findIter->Handle; + nHitCount++; + } + } + return nHitCount; +} + +namespace internal +{ + class PropertyForwarder + { + private: + OPropertySetAggregationHelper& m_rAggregationHelper; + o3tl::sorted_vector< sal_Int32 > m_aProperties; + sal_Int32 m_nCurrentlyForwarding; + + public: + explicit PropertyForwarder( OPropertySetAggregationHelper& _rAggregationHelper ); + + /** declares that the forwarder should be responsible for the given property + + @param _nHandle + the public handle (<em>not</em> the original handle!) of the property + */ + void takeResponsibilityFor( sal_Int32 _nHandle ); + + /** checks whether the forwarder is responsible for the given property + */ + bool isResponsibleFor( sal_Int32 _nHandle ) const; + + /// actually forwards a property value to the aggregate + /// + /// @throws Exception + void doForward( sal_Int32 _nHandle, const Any& _rValue ); + + sal_Int32 getCurrentlyForwardedProperty( ) const { return m_nCurrentlyForwarding; } + }; + + + PropertyForwarder::PropertyForwarder( OPropertySetAggregationHelper& _rAggregationHelper ) + :m_rAggregationHelper( _rAggregationHelper ) + ,m_nCurrentlyForwarding( -1 ) + { + } + + + void PropertyForwarder::takeResponsibilityFor( sal_Int32 _nHandle ) + { + m_aProperties.insert( _nHandle ); + } + + + bool PropertyForwarder::isResponsibleFor( sal_Int32 _nHandle ) const + { + return m_aProperties.find( _nHandle ) != m_aProperties.end(); + } + + + void PropertyForwarder::doForward( sal_Int32 _nHandle, const Any& _rValue ) + { + OSL_ENSURE( m_rAggregationHelper.m_xAggregateSet.is(), "PropertyForwarder::doForward: no property set!" ); + if ( !m_rAggregationHelper.m_xAggregateSet.is() ) + return; + + m_rAggregationHelper.forwardingPropertyValue( _nHandle ); + + OSL_ENSURE( m_nCurrentlyForwarding == -1, "PropertyForwarder::doForward: reentrance?" ); + m_nCurrentlyForwarding = _nHandle; + + try + { + m_rAggregationHelper.m_xAggregateSet->setPropertyValue( m_rAggregationHelper.getPropertyName( _nHandle ), _rValue ); + // TODO: cache the property name? (it's a O(log n) search) + } + catch( const Exception& ) + { + m_rAggregationHelper.forwardedPropertyValue( _nHandle ); + throw; + } + + m_nCurrentlyForwarding = -1; + + m_rAggregationHelper.forwardedPropertyValue( _nHandle ); + } +} + +OPropertySetAggregationHelper::OPropertySetAggregationHelper( ::cppu::OBroadcastHelper& rBHlp ) + :OPropertyStateHelper( rBHlp ) + ,m_bListening( false ) +{ + m_pForwarder.reset( new PropertyForwarder( *this ) ); +} + + +OPropertySetAggregationHelper::~OPropertySetAggregationHelper() +{ +} + + +css::uno::Any SAL_CALL OPropertySetAggregationHelper::queryInterface(const css::uno::Type& _rType) +{ + css::uno::Any aReturn = OPropertyStateHelper::queryInterface(_rType); + + if ( !aReturn.hasValue() ) + aReturn = cppu::queryInterface(_rType + ,static_cast< css::beans::XPropertiesChangeListener*>(this) + ,static_cast< css::beans::XVetoableChangeListener*>(this) + ,static_cast< css::lang::XEventListener*>(static_cast< css::beans::XPropertiesChangeListener*>(this)) + ); + + return aReturn; +} + + +void OPropertySetAggregationHelper::disposing() +{ + osl::MutexGuard aGuard(rBHelper.rMutex); + + if ( m_xAggregateSet.is() && m_bListening ) + { + // register as a single listener + m_xAggregateMultiSet->removePropertiesChangeListener(this); + m_xAggregateSet->removeVetoableChangeListener(OUString(), this); + m_bListening = false; + } + + OPropertyStateHelper::disposing(); +} + + +void SAL_CALL OPropertySetAggregationHelper::disposing(const css::lang::EventObject& _rSource) +{ + OSL_ENSURE(m_xAggregateSet.is(), "OPropertySetAggregationHelper::disposing : don't have an aggregate anymore !"); + if (_rSource.Source == m_xAggregateSet) + m_bListening = false; +} + + +void SAL_CALL OPropertySetAggregationHelper::propertiesChange(const css::uno::Sequence< css::beans::PropertyChangeEvent>& _rEvents) +{ + OSL_ENSURE(m_xAggregateSet.is(), "OPropertySetAggregationHelper::propertiesChange : have no aggregate !"); + + sal_Int32 nLen = _rEvents.getLength(); + cppu::IPropertyArrayHelper& rPH = getInfoHelper(); + + if (1 == nLen) + { + const css::beans::PropertyChangeEvent& evt = _rEvents.getConstArray()[0]; + OSL_ENSURE(!evt.PropertyName.isEmpty(), "OPropertySetAggregationHelper::propertiesChange : invalid event !"); + // we had a bug where this assertion would have us saved a whole day :) (72514) + sal_Int32 nHandle = rPH.getHandleByName( evt.PropertyName ); + + // If nHandle is -1 the event marks a (aggregate) property which we hide to callers + // If isCurrentlyForwardingProperty( nHandle ) is <TRUE/>, then we ourself triggered + // setting this property. In this case, it will be notified later (by the OPropertySetHelper + // implementation) + + if ( ( nHandle != -1 ) && !isCurrentlyForwardingProperty( nHandle ) ) + fire(&nHandle, &evt.NewValue, &evt.OldValue, 1, false); + } + else + { + std::unique_ptr<sal_Int32[]> pHandles(new sal_Int32[nLen]); + std::unique_ptr< css::uno::Any[]> pNewValues(new css::uno::Any[nLen]); + std::unique_ptr< css::uno::Any[]> pOldValues(new css::uno::Any[nLen]); + + sal_Int32 nDest = 0; + for (const css::beans::PropertyChangeEvent& rEvent : _rEvents) + { + sal_Int32 nHandle = rPH.getHandleByName(rEvent.PropertyName); + if ( ( nHandle != -1 ) && !isCurrentlyForwardingProperty( nHandle ) ) + { // same as above : -1 is valid (73247) ... + pHandles[nDest] = nHandle; + pNewValues[nDest] = rEvent.NewValue; + pOldValues[nDest] = rEvent.OldValue; + ++nDest; + } + } + + if (nDest) + fire(pHandles.get(), pNewValues.get(), pOldValues.get(), nDest, false); + } +} + + +void SAL_CALL OPropertySetAggregationHelper::vetoableChange(const css::beans::PropertyChangeEvent& _rEvent) +{ + OSL_ENSURE(m_xAggregateSet.is(), "OPropertySetAggregationHelper::vetoableChange : have no aggregate !"); + + cppu::IPropertyArrayHelper& rPH = getInfoHelper(); + + sal_Int32 nHandle = rPH.getHandleByName(_rEvent.PropertyName); + fire(&nHandle, &_rEvent.NewValue, &_rEvent.OldValue, 1, true); +} + + +void OPropertySetAggregationHelper::setAggregation(const css::uno::Reference< css::uno::XInterface >& _rxDelegate) +{ + osl::MutexGuard aGuard(rBHelper.rMutex); + + if (m_bListening && m_xAggregateSet.is()) + { + m_xAggregateMultiSet->removePropertiesChangeListener(this); + m_xAggregateSet->removeVetoableChangeListener(OUString(), this); + m_bListening = false; + } + + m_xAggregateState.set(_rxDelegate, css::uno::UNO_QUERY); + m_xAggregateSet.set(_rxDelegate, css::uno::UNO_QUERY); + m_xAggregateMultiSet.set(_rxDelegate, css::uno::UNO_QUERY); + m_xAggregateFastSet.set(_rxDelegate, css::uno::UNO_QUERY); + + // must support XPropertySet and XMultiPropertySet + if ( m_xAggregateSet.is() && !m_xAggregateMultiSet.is() ) + throw css::lang::IllegalArgumentException(); +} + + +void OPropertySetAggregationHelper::startListening() +{ + osl::MutexGuard aGuard(rBHelper.rMutex); + + if (!m_bListening && m_xAggregateSet.is()) + { + // register as a single listener + css::uno::Sequence< OUString > aPropertyNames; + m_xAggregateMultiSet->addPropertiesChangeListener(aPropertyNames, this); + m_xAggregateSet->addVetoableChangeListener(OUString(), this); + + m_bListening = true; + } +} + + +void SAL_CALL OPropertySetAggregationHelper::addVetoableChangeListener(const OUString& _rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener>& _rxListener) +{ + OPropertySetHelper::addVetoableChangeListener(_rPropertyName, _rxListener); + if (!m_bListening) + startListening(); +} + + +void SAL_CALL OPropertySetAggregationHelper::addPropertyChangeListener(const OUString& _rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener>& _rxListener) +{ + OPropertySetHelper::addPropertyChangeListener(_rPropertyName, _rxListener); + if (!m_bListening) + startListening(); +} + + +void SAL_CALL OPropertySetAggregationHelper::addPropertiesChangeListener(const css::uno::Sequence< OUString >& _rPropertyNames, + const css::uno::Reference< css::beans::XPropertiesChangeListener>& _rxListener) +{ + OPropertySetHelper::addPropertiesChangeListener(_rPropertyNames, _rxListener); + if (!m_bListening) + startListening(); +} + + +sal_Int32 OPropertySetAggregationHelper::getOriginalHandle(sal_Int32 nHandle) const +{ + OPropertyArrayAggregationHelper& rPH = static_cast<OPropertyArrayAggregationHelper&>( const_cast<OPropertySetAggregationHelper*>(this)->getInfoHelper() ); + sal_Int32 nOriginalHandle = -1; + (void)rPH.fillAggregatePropertyInfoByHandle(nullptr, &nOriginalHandle, nHandle); + return nOriginalHandle; +} + + +OUString OPropertySetAggregationHelper::getPropertyName( sal_Int32 _nHandle ) const +{ + OPropertyArrayAggregationHelper& rPH = static_cast< OPropertyArrayAggregationHelper& >( const_cast<OPropertySetAggregationHelper*>(this)->getInfoHelper() ); + Property aProperty; + OSL_VERIFY( rPH.getPropertyByHandle( _nHandle, aProperty ) ); + return aProperty.Name; +} + + +void SAL_CALL OPropertySetAggregationHelper::setFastPropertyValue(sal_Int32 _nHandle, const css::uno::Any& _rValue) +{ + OPropertyArrayAggregationHelper& rPH = static_cast< OPropertyArrayAggregationHelper& >( getInfoHelper() ); + OUString aPropName; + sal_Int32 nOriginalHandle = -1; + + // does the handle belong to the aggregation ? + if (rPH.fillAggregatePropertyInfoByHandle(&aPropName, &nOriginalHandle, _nHandle)) + if (m_xAggregateFastSet.is()) + m_xAggregateFastSet->setFastPropertyValue(nOriginalHandle, _rValue); + else + m_xAggregateSet->setPropertyValue(aPropName, _rValue); + else + OPropertySetHelper::setFastPropertyValue(_nHandle, _rValue); +} + + +void OPropertySetAggregationHelper::getFastPropertyValue( css::uno::Any& rValue, sal_Int32 nHandle) const +{ + OPropertyArrayAggregationHelper& rPH = static_cast<OPropertyArrayAggregationHelper&>( const_cast<OPropertySetAggregationHelper*>(this)->getInfoHelper() ); + OUString aPropName; + sal_Int32 nOriginalHandle = -1; + + if (rPH.fillAggregatePropertyInfoByHandle(&aPropName, &nOriginalHandle, nHandle)) + { + if (m_xAggregateFastSet.is()) + rValue = m_xAggregateFastSet->getFastPropertyValue(nOriginalHandle); + else + rValue = m_xAggregateSet->getPropertyValue(aPropName); + } + else if ( m_pForwarder->isResponsibleFor( nHandle ) ) + { + // this is a property which has been "overwritten" in our instance (thus + // fillAggregatePropertyInfoByHandle didn't find it) + rValue = m_xAggregateSet->getPropertyValue( getPropertyName( nHandle ) ); + } +} + + +css::uno::Any SAL_CALL OPropertySetAggregationHelper::getFastPropertyValue(sal_Int32 nHandle) +{ + OPropertyArrayAggregationHelper& rPH = static_cast< OPropertyArrayAggregationHelper& >( getInfoHelper() ); + OUString aPropName; + sal_Int32 nOriginalHandle = -1; + css::uno::Any aValue; + + if (rPH.fillAggregatePropertyInfoByHandle(&aPropName, &nOriginalHandle, nHandle)) + { + if (m_xAggregateFastSet.is()) + aValue = m_xAggregateFastSet->getFastPropertyValue(nOriginalHandle); + else + aValue = m_xAggregateSet->getPropertyValue(aPropName); + } + else + aValue = OPropertySetHelper::getFastPropertyValue(nHandle); + + return aValue; +} + + +void SAL_CALL OPropertySetAggregationHelper::setPropertyValues( + const Sequence< OUString >& _rPropertyNames, const Sequence< Any >& _rValues ) +{ + OSL_ENSURE( !rBHelper.bInDispose, "OPropertySetAggregationHelper::setPropertyValues : do not use within the dispose call !"); + OSL_ENSURE( !rBHelper.bDisposed, "OPropertySetAggregationHelper::setPropertyValues : object is disposed" ); + + // check where the properties come from + if (!m_xAggregateSet.is()) + OPropertySetHelper::setPropertyValues(_rPropertyNames, _rValues); + else if (_rPropertyNames.getLength() == 1) // use the more efficient way + { + try + { + setPropertyValue( _rPropertyNames[0], _rValues[0] ); + } + catch( const UnknownPropertyException& ) + { + // by definition of XMultiPropertySet::setPropertyValues, unknown properties are to be ignored + SAL_WARN( "comphelper", "OPropertySetAggregationHelper::setPropertyValues: unknown property: '" + << _rPropertyNames[0] << "', implementation: " << typeid( *this ).name() ); + } + } + else + { + OPropertyArrayAggregationHelper& rPH = static_cast< OPropertyArrayAggregationHelper& >( getInfoHelper() ); + + // determine which properties belong to the aggregate, and which ones to the delegator + sal_Int32 nAggCount(0); + sal_Int32 nLen(_rPropertyNames.getLength()); + + for ( const OUString& rName : _rPropertyNames ) + { + OPropertyArrayAggregationHelper::PropertyOrigin ePropOrg = rPH.classifyProperty( rName ); + if ( OPropertyArrayAggregationHelper::PropertyOrigin::Unknown == ePropOrg ) + throw WrappedTargetException( OUString(), static_cast< XMultiPropertySet* >( this ), Any( UnknownPropertyException( ) ) ); + // due to a flaw in the API design, this method is not allowed to throw an UnknownPropertyException + // so we wrap it into a WrappedTargetException + + if ( OPropertyArrayAggregationHelper::PropertyOrigin::Aggregate == ePropOrg ) + ++nAggCount; + } + + // all properties belong to the aggregate + if (nAggCount == nLen) + m_xAggregateMultiSet->setPropertyValues(_rPropertyNames, _rValues); + + // all properties belong to the aggregating object + else if (nAggCount == 0) + OPropertySetHelper::setPropertyValues(_rPropertyNames, _rValues); + + // mixed + else + { + const css::uno::Any* pValues = _rValues.getConstArray(); + + // dividing the Names and _rValues + + // aggregate's names + Sequence< OUString > AggPropertyNames( nAggCount ); + OUString* pAggNames = AggPropertyNames.getArray(); + // aggregate's values + Sequence< Any > AggValues( nAggCount ); + Any* pAggValues = AggValues.getArray(); + + // delegator names + Sequence< OUString > DelPropertyNames( nLen - nAggCount ); + OUString* pDelNames = DelPropertyNames.getArray(); + + // delegator values + Sequence< Any > DelValues( nLen - nAggCount ); + Any* pDelValues = DelValues.getArray(); + + for ( const OUString& rName : _rPropertyNames ) + { + if ( OPropertyArrayAggregationHelper::PropertyOrigin::Aggregate == rPH.classifyProperty( rName ) ) + { + *pAggNames++ = rName; + *pAggValues++ = *pValues++; + } + else + { + *pDelNames++ = rName; + *pDelValues++ = *pValues++; + } + } + + // reset, needed below + pDelValues = DelValues.getArray(); + + std::unique_ptr<sal_Int32[]> pHandles(new sal_Int32[ nLen - nAggCount ]); + + // get the map table + cppu::IPropertyArrayHelper& rPH2 = getInfoHelper(); + + // fill the handle array + sal_Int32 nHitCount = rPH2.fillHandles( pHandles.get(), DelPropertyNames ); + if (nHitCount != 0) + { + std::unique_ptr< css::uno::Any[]> pConvertedValues(new css::uno::Any[ nHitCount ]); + std::unique_ptr< css::uno::Any[]> pOldValues(new css::uno::Any[ nHitCount ]); + nHitCount = 0; + sal_Int32 i; + + { + // must lock the mutex outside the loop. So all values are consistent. + osl::MutexGuard aGuard( rBHelper.rMutex ); + for( i = 0; i < (nLen - nAggCount); ++i ) + { + if( pHandles[i] != -1 ) + { + sal_Int16 nAttributes; + rPH2.fillPropertyMembersByHandle( nullptr, &nAttributes, pHandles[i] ); + if( nAttributes & css::beans::PropertyAttribute::READONLY ) + throw css::beans::PropertyVetoException(); + // Will the property change? + if( convertFastPropertyValue( pConvertedValues[ nHitCount ], pOldValues[nHitCount], + pHandles[i], pDelValues[i] ) ) + { + // only increment if the property really change + pHandles[nHitCount] = pHandles[i]; + nHitCount++; + } + } + } + // release guard to fire events + } + + // fire vetoable events + fire( pHandles.get(), pConvertedValues.get(), pOldValues.get(), nHitCount, true ); + + // setting the agg Properties + m_xAggregateMultiSet->setPropertyValues(AggPropertyNames, AggValues); + + { + // must lock the mutex outside the loop. + osl::MutexGuard aGuard( rBHelper.rMutex ); + // Loop over all changed properties + for( i = 0; i < nHitCount; i++ ) + { + // Will the property change? + setFastPropertyValue_NoBroadcast( pHandles[i], pConvertedValues[i] ); + } + // release guard to fire events + } + + // fire change events + fire( pHandles.get(), pConvertedValues.get(), pOldValues.get(), nHitCount, false ); + } + else + m_xAggregateMultiSet->setPropertyValues(AggPropertyNames, AggValues); + } + } +} + +// XPropertyState + +css::beans::PropertyState SAL_CALL OPropertySetAggregationHelper::getPropertyState(const OUString& _rPropertyName) +{ + OPropertyArrayAggregationHelper& rPH = static_cast< OPropertyArrayAggregationHelper& >( getInfoHelper() ); + sal_Int32 nHandle = rPH.getHandleByName( _rPropertyName ); + + if (nHandle == -1) + { + throw css::beans::UnknownPropertyException(_rPropertyName); + } + + OUString aPropName; + sal_Int32 nOriginalHandle = -1; + if (rPH.fillAggregatePropertyInfoByHandle(&aPropName, &nOriginalHandle, nHandle)) + { + if (m_xAggregateState.is()) + return m_xAggregateState->getPropertyState(_rPropertyName); + else + return css::beans::PropertyState_DIRECT_VALUE; + } + else + return getPropertyStateByHandle(nHandle); +} + + +void SAL_CALL OPropertySetAggregationHelper::setPropertyToDefault(const OUString& _rPropertyName) +{ + OPropertyArrayAggregationHelper& rPH = static_cast< OPropertyArrayAggregationHelper& >( getInfoHelper() ); + sal_Int32 nHandle = rPH.getHandleByName(_rPropertyName); + if (nHandle == -1) + { + throw css::beans::UnknownPropertyException(_rPropertyName); + } + + OUString aPropName; + sal_Int32 nOriginalHandle = -1; + if (rPH.fillAggregatePropertyInfoByHandle(&aPropName, &nOriginalHandle, nHandle)) + { + if (m_xAggregateState.is()) + m_xAggregateState->setPropertyToDefault(_rPropertyName); + } + else + { + try + { + setPropertyToDefaultByHandle( nHandle ); + } + catch( const UnknownPropertyException& ) { throw; } + catch( const RuntimeException& ) { throw; } + catch( const Exception& ) + { + OSL_FAIL( "OPropertySetAggregationHelper::setPropertyToDefault: caught an exception which is not allowed to leave here!" ); + } + } +} + + +css::uno::Any SAL_CALL OPropertySetAggregationHelper::getPropertyDefault(const OUString& aPropertyName) +{ + OPropertyArrayAggregationHelper& rPH = static_cast< OPropertyArrayAggregationHelper& >( getInfoHelper() ); + sal_Int32 nHandle = rPH.getHandleByName( aPropertyName ); + + if ( nHandle == -1 ) + throw css::beans::UnknownPropertyException(aPropertyName); + + OUString aPropName; + sal_Int32 nOriginalHandle = -1; + if (rPH.fillAggregatePropertyInfoByHandle(&aPropName, &nOriginalHandle, nHandle)) + { + if (m_xAggregateState.is()) + return m_xAggregateState->getPropertyDefault(aPropertyName); + else + return css::uno::Any(); + } + else + return getPropertyDefaultByHandle(nHandle); +} + +sal_Bool SAL_CALL OPropertySetAggregationHelper::convertFastPropertyValue( Any& _rConvertedValue, Any& _rOldValue, sal_Int32 _nHandle, const Any& _rValue ) +{ + bool bModified = false; + + OSL_ENSURE( m_pForwarder->isResponsibleFor( _nHandle ), "OPropertySetAggregationHelper::convertFastPropertyValue: this is no forwarded property - did you use declareForwardedProperty for it?" ); + if ( m_pForwarder->isResponsibleFor( _nHandle ) ) + { + // need to determine the type of the property for conversion + OPropertyArrayAggregationHelper& rPH = static_cast< OPropertyArrayAggregationHelper& >( getInfoHelper() ); + Property aProperty; + OSL_VERIFY( rPH.getPropertyByHandle( _nHandle, aProperty ) ); + + Any aCurrentValue; + getFastPropertyValue( aCurrentValue, _nHandle ); + bModified = tryPropertyValue( _rConvertedValue, _rOldValue, _rValue, aCurrentValue, aProperty.Type ); + } + + return bModified; +} + +void SAL_CALL OPropertySetAggregationHelper::setFastPropertyValue_NoBroadcast( sal_Int32 _nHandle, const Any& _rValue ) +{ + OSL_ENSURE( m_pForwarder->isResponsibleFor( _nHandle ), "OPropertySetAggregationHelper::setFastPropertyValue_NoBroadcast: this is no forwarded property - did you use declareForwardedProperty for it?" ); + if ( m_pForwarder->isResponsibleFor( _nHandle ) ) + m_pForwarder->doForward( _nHandle, _rValue ); +} + + +void OPropertySetAggregationHelper::declareForwardedProperty( sal_Int32 _nHandle ) +{ + OSL_ENSURE( !m_pForwarder->isResponsibleFor( _nHandle ), "OPropertySetAggregationHelper::declareForwardedProperty: already declared!" ); + m_pForwarder->takeResponsibilityFor( _nHandle ); +} + + +void OPropertySetAggregationHelper::forwardingPropertyValue( sal_Int32 ) +{ + // not interested in +} + + +void OPropertySetAggregationHelper::forwardedPropertyValue( sal_Int32 ) +{ + // not interested in +} + + +bool OPropertySetAggregationHelper::isCurrentlyForwardingProperty( sal_Int32 _nHandle ) const +{ + return m_pForwarder->getCurrentlyForwardedProperty() == _nHandle; +} + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/property.cxx b/comphelper/source/property/property.cxx new file mode 100644 index 000000000..49a7a108f --- /dev/null +++ b/comphelper/source/property/property.cxx @@ -0,0 +1,203 @@ +/* -*- 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/property.hxx> +#include <comphelper/sequence.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#if OSL_DEBUG_LEVEL > 0 + #include <cppuhelper/exc_hlp.hxx> + #include <com/sun/star/lang/XServiceInfo.hpp> + #include <typeinfo> +#endif +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <rtl/ustrbuf.hxx> +#include <algorithm> + + +namespace comphelper +{ + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::beans::XPropertySet; + using ::com::sun::star::beans::XPropertySetInfo; + using ::com::sun::star::beans::Property; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Type; + using ::com::sun::star::uno::cpp_queryInterface; + using ::com::sun::star::uno::cpp_acquire; + using ::com::sun::star::uno::cpp_release; +#if OSL_DEBUG_LEVEL > 0 + using ::com::sun::star::lang::XServiceInfo; +#endif + using ::com::sun::star::uno::UNO_QUERY; + + namespace PropertyAttribute = ::com::sun::star::beans::PropertyAttribute; + + +void copyProperties(const Reference<XPropertySet>& _rxSource, + const Reference<XPropertySet>& _rxDest) +{ + if (!_rxSource.is() || !_rxDest.is()) + { + OSL_FAIL("copyProperties: invalid arguments !"); + return; + } + + Reference< XPropertySetInfo > xSourceProps = _rxSource->getPropertySetInfo(); + Reference< XPropertySetInfo > xDestProps = _rxDest->getPropertySetInfo(); + + const Sequence< Property > aSourceProps = xSourceProps->getProperties(); + Property aDestProp; + for (const Property& rSourceProp : aSourceProps) + { + if ( xDestProps->hasPropertyByName(rSourceProp.Name) ) + { + try + { + aDestProp = xDestProps->getPropertyByName(rSourceProp.Name); + if (0 == (aDestProp.Attributes & PropertyAttribute::READONLY) ) + { + const Any aSourceValue = _rxSource->getPropertyValue(rSourceProp.Name); + if ( 0 != (aDestProp.Attributes & PropertyAttribute::MAYBEVOID) || aSourceValue.hasValue() ) + _rxDest->setPropertyValue(rSourceProp.Name, aSourceValue); + } + } + catch (Exception&) + { +#if OSL_DEBUG_LEVEL > 0 + OUStringBuffer aBuffer; + aBuffer.append( "::comphelper::copyProperties: could not copy property '" ); + aBuffer.append(rSourceProp.Name ); + aBuffer.append( "' to the destination set (a '" ); + + Reference< XServiceInfo > xSI( _rxDest, UNO_QUERY ); + if ( xSI.is() ) + { + aBuffer.append( xSI->getImplementationName() ); + } + else + { + aBuffer.append( OUString::createFromAscii(typeid( *_rxDest ).name()) ); + } + aBuffer.append( "' implementation).\n" ); + + Any aException( ::cppu::getCaughtException() ); + aBuffer.append( "Caught an exception of type '" ); + aBuffer.append( aException.getValueTypeName() ); + aBuffer.append( "'" ); + + Exception aBaseException; + if ( ( aException >>= aBaseException ) && !aBaseException.Message.isEmpty() ) + { + aBuffer.append( ", saying '" ); + aBuffer.append( aBaseException.Message ); + aBuffer.append( "'" ); + } + aBuffer.append( "." ); + + SAL_WARN( "comphelper", aBuffer.makeStringAndClear() ); +#endif + } + } + } +} + + +bool hasProperty(const OUString& _rName, const Reference<XPropertySet>& _rxSet) +{ + if (_rxSet.is()) + { + // XPropertySetInfoRef xInfo(rxSet->getPropertySetInfo()); + return _rxSet->getPropertySetInfo()->hasPropertyByName(_rName); + } + return false; +} + + +void RemoveProperty(Sequence<Property>& _rProps, const OUString& _rPropName) +{ + // binary search + Property aNameProp(_rPropName, 0, Type(), 0); + const Property* pResult = std::lower_bound(std::cbegin(_rProps), std::cend(_rProps), aNameProp, PropertyCompareByName()); + + if ( pResult != std::cend(_rProps) && pResult->Name == _rPropName) + { + removeElementAt(_rProps, pResult - std::cbegin(_rProps)); + } +} + + +void ModifyPropertyAttributes(Sequence<Property>& seqProps, const OUString& sPropName, sal_Int16 nAddAttrib, sal_Int16 nRemoveAttrib) +{ + // binary search + auto [begin, end] = asNonConstRange(seqProps); + Property aNameProp(sPropName, 0, Type(), 0); + Property* pResult = std::lower_bound(begin, end, aNameProp, PropertyCompareByName()); + + if ( (pResult != end) && (pResult->Name == sPropName) ) + { + pResult->Attributes |= nAddAttrib; + pResult->Attributes &= ~nRemoveAttrib; + } +} + + +bool tryPropertyValue(Any& _rConvertedValue, Any& _rOldValue, const Any& _rValueToSet, const Any& _rCurrentValue, const Type& _rExpectedType) +{ + bool bModified(false); + if (_rCurrentValue.getValue() != _rValueToSet.getValue()) + { + if ( _rValueToSet.hasValue() && ( !_rExpectedType.equals( _rValueToSet.getValueType() ) ) ) + { + _rConvertedValue = Any( nullptr, _rExpectedType.getTypeLibType() ); + + if ( !uno_type_assignData( + const_cast< void* >( _rConvertedValue.getValue() ), _rConvertedValue.getValueType().getTypeLibType(), + const_cast< void* >( _rValueToSet.getValue() ), _rValueToSet.getValueType().getTypeLibType(), + reinterpret_cast< uno_QueryInterfaceFunc >( + cpp_queryInterface), + reinterpret_cast< uno_AcquireFunc >(cpp_acquire), + reinterpret_cast< uno_ReleaseFunc >(cpp_release) + ) + ) + throw css::lang::IllegalArgumentException(); + } + else + _rConvertedValue = _rValueToSet; + + if ( _rCurrentValue != _rConvertedValue ) + { + _rOldValue = _rCurrentValue; + bModified = true; + } + } + return bModified; +} + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/propertybag.cxx b/comphelper/source/property/propertybag.cxx new file mode 100644 index 000000000..02e6f78c1 --- /dev/null +++ b/comphelper/source/property/propertybag.cxx @@ -0,0 +1,206 @@ +/* -*- 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/propertybag.hxx> +#include <osl/diagnose.h> + +#include <com/sun/star/beans/IllegalTypeException.hpp> +#include <com/sun/star/beans/PropertyExistException.hpp> +#include <com/sun/star/container/ElementExistException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/NotRemoveableException.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> + +#include <map> +#include <string_view> + +namespace comphelper +{ + + + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Type; + using ::com::sun::star::uno::TypeClass_VOID; + using ::com::sun::star::beans::IllegalTypeException; + using ::com::sun::star::beans::PropertyExistException; + using ::com::sun::star::container::ElementExistException; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::beans::Property; + using ::com::sun::star::beans::NotRemoveableException; + using ::com::sun::star::beans::UnknownPropertyException; + + namespace PropertyAttribute = ::com::sun::star::beans::PropertyAttribute; + + PropertyBag::PropertyBag() + : m_bAllowEmptyPropertyName(false) + { + } + + PropertyBag::~PropertyBag() + { + } + + + void PropertyBag::setAllowEmptyPropertyName( bool i_isAllowed ) + { + m_bAllowEmptyPropertyName = i_isAllowed; + } + + + namespace + { + void lcl_checkForEmptyName( const bool _allowEmpty, std::u16string_view _name ) + { + if ( !_allowEmpty && _name.empty() ) + throw IllegalArgumentException( + "The property name must not be empty.", + // TODO: resource + nullptr, + 1 + ); + } + + void lcl_checkNameAndHandle_PropertyExistException( const OUString& _name, const sal_Int32 _handle, const PropertyBag& _container ) + { + if ( _container.hasPropertyByName( _name ) || _container.hasPropertyByHandle( _handle ) ) + throw PropertyExistException( + "Property name or handle already used.", + nullptr ); + + } + + void lcl_checkNameAndHandle_ElementExistException( const OUString& _name, const sal_Int32 _handle, const PropertyBag& _container ) + { + if ( _container.hasPropertyByName( _name ) || _container.hasPropertyByHandle( _handle ) ) + throw ElementExistException( + "Property name or handle already used.", + nullptr ); + + } + + } + + + void PropertyBag::addVoidProperty( const OUString& _rName, const Type& _rType, sal_Int32 _nHandle, sal_Int32 _nAttributes ) + { + if ( _rType.getTypeClass() == TypeClass_VOID ) + throw IllegalArgumentException( + "Illegal property type: VOID", + // TODO: resource + nullptr, + 1 + ); + + // check name/handle sanity + lcl_checkForEmptyName( m_bAllowEmptyPropertyName, _rName ); + lcl_checkNameAndHandle_ElementExistException( _rName, _nHandle, *this ); + + // register the property + OSL_ENSURE( _nAttributes & PropertyAttribute::MAYBEVOID, "PropertyBag::addVoidProperty: this is for default-void properties only!" ); + registerPropertyNoMember( _rName, _nHandle, _nAttributes | PropertyAttribute::MAYBEVOID, _rType, css::uno::Any() ); + + // remember the default + aDefaults.emplace( _nHandle, Any() ); + } + + + void PropertyBag::addProperty( const OUString& _rName, sal_Int32 _nHandle, sal_Int32 _nAttributes, const Any& _rInitialValue ) + { + // check type sanity + const Type& aPropertyType = _rInitialValue.getValueType(); + if ( aPropertyType.getTypeClass() == TypeClass_VOID ) + throw IllegalTypeException( + "The initial value must be non-NULL to determine the property type.", + // TODO: resource + nullptr ); + + // check name/handle sanity + lcl_checkForEmptyName( m_bAllowEmptyPropertyName, _rName ); + lcl_checkNameAndHandle_PropertyExistException( _rName, _nHandle, *this ); + + // register the property + registerPropertyNoMember( _rName, _nHandle, _nAttributes, aPropertyType, + _rInitialValue ); + + // remember the default + aDefaults.emplace( _nHandle, _rInitialValue ); + } + + + void PropertyBag::removeProperty( const OUString& _rName ) + { + const Property& rProp = getProperty( _rName ); + // will throw an UnknownPropertyException if necessary + if ( ( rProp.Attributes & PropertyAttribute::REMOVABLE ) == 0 ) + throw NotRemoveableException( OUString(), nullptr ); + const sal_Int32 nHandle = rProp.Handle; + + revokeProperty( nHandle ); + + aDefaults.erase( nHandle ); + } + + + void PropertyBag::getFastPropertyValue( sal_Int32 _nHandle, Any& _out_rValue ) const + { + if ( !hasPropertyByHandle( _nHandle ) ) + throw UnknownPropertyException(OUString::number(_nHandle)); + + OPropertyContainerHelper::getFastPropertyValue( _out_rValue, _nHandle ); + } + + + bool PropertyBag::convertFastPropertyValue( sal_Int32 _nHandle, const Any& _rNewValue, Any& _out_rConvertedValue, Any& _out_rCurrentValue ) const + { + if ( !hasPropertyByHandle( _nHandle ) ) + throw UnknownPropertyException(OUString::number(_nHandle)); + + return const_cast< PropertyBag* >( this )->OPropertyContainerHelper::convertFastPropertyValue( + _out_rConvertedValue, _out_rCurrentValue, _nHandle, _rNewValue ); + } + + + void PropertyBag::setFastPropertyValue( sal_Int32 _nHandle, const Any& _rValue ) + { + if ( !hasPropertyByHandle( _nHandle ) ) + throw UnknownPropertyException(OUString::number(_nHandle)); + + OPropertyContainerHelper::setFastPropertyValue( _nHandle, _rValue ); + } + + + void PropertyBag::getPropertyDefaultByHandle( sal_Int32 _nHandle, Any& _out_rValue ) const + { + if ( !hasPropertyByHandle( _nHandle ) ) + throw UnknownPropertyException(OUString::number(_nHandle)); + + auto pos = aDefaults.find( _nHandle ); + OSL_ENSURE( pos != aDefaults.end(), "PropertyBag::getPropertyDefaultByHandle: inconsistency!" ); + if ( pos != aDefaults.end() ) + _out_rValue = pos->second; + else + _out_rValue.clear(); + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/propertycontainer.cxx b/comphelper/source/property/propertycontainer.cxx new file mode 100644 index 000000000..2b5685405 --- /dev/null +++ b/comphelper/source/property/propertycontainer.cxx @@ -0,0 +1,76 @@ +/* -*- 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/propertycontainer.hxx> +#include <cppuhelper/typeprovider.hxx> + + +namespace comphelper +{ + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; + +OPropertyContainer::OPropertyContainer(::cppu::OBroadcastHelper& _rBHelper) + :OPropertySetHelper(_rBHelper) +{ +} + + +OPropertyContainer::~OPropertyContainer() +{ +} + + +Sequence< Type > OPropertyContainer::getBaseTypes() +{ + // just the types from our one and only base class + ::cppu::OTypeCollection aTypes( + cppu::UnoType<XPropertySet>::get(), + cppu::UnoType<XFastPropertySet>::get(), + cppu::UnoType<XMultiPropertySet>::get() + ); + return aTypes.getTypes(); +} + +sal_Bool OPropertyContainer::convertFastPropertyValue( + Any& _rConvertedValue, Any& _rOldValue, sal_Int32 _nHandle, const Any& _rValue ) +{ + return OPropertyContainerHelper::convertFastPropertyValue( _rConvertedValue, _rOldValue, _nHandle, _rValue ); +} + + +void OPropertyContainer::setFastPropertyValue_NoBroadcast(sal_Int32 _nHandle, const Any& _rValue) +{ + OPropertyContainerHelper::setFastPropertyValue( _nHandle, _rValue ); +} + + +void OPropertyContainer::getFastPropertyValue(Any& _rValue, sal_Int32 _nHandle) const +{ + OPropertyContainerHelper::getFastPropertyValue( _rValue, _nHandle ); +} + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/propertycontainerhelper.cxx b/comphelper/source/property/propertycontainerhelper.cxx new file mode 100644 index 000000000..ee81100ae --- /dev/null +++ b/comphelper/source/property/propertycontainerhelper.cxx @@ -0,0 +1,494 @@ +/* -*- 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/propertycontainerhelper.hxx> +#include <comphelper/property.hxx> +#include <osl/diagnose.h> +#include <uno/data.h> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> + +#include <algorithm> +#include <utility> + + +namespace comphelper +{ + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; + + +namespace +{ + // comparing two property descriptions + struct PropertyDescriptionHandleCompare + { + bool operator() (const PropertyDescription& x, const PropertyDescription& y) const + { + return x.aProperty.Handle < y.aProperty.Handle; + } + }; + // comparing two property descriptions (by name) + struct PropertyDescriptionNameMatch + { + OUString const m_rCompare; + explicit PropertyDescriptionNameMatch( OUString _aCompare ) : m_rCompare(std::move( _aCompare )) { } + + bool operator() (const PropertyDescription& x ) const + { + return x.aProperty.Name == m_rCompare; + } + }; +} + +OPropertyContainerHelper::OPropertyContainerHelper() +{ +} + + +OPropertyContainerHelper::~OPropertyContainerHelper() +{ +} + + +void OPropertyContainerHelper::registerProperty(const OUString& _rName, sal_Int32 _nHandle, + sal_Int32 _nAttributes, void* _pPointerToMember, const Type& _rMemberType) +{ + OSL_ENSURE((_nAttributes & PropertyAttribute::MAYBEVOID) == 0, + "OPropertyContainerHelper::registerProperty: don't use this for properties which may be void ! There is a method called \"registerMayBeVoidProperty\" for this !"); + OSL_ENSURE(!_rMemberType.equals(cppu::UnoType<Any>::get()), + "OPropertyContainerHelper::registerProperty: don't give my the type of a uno::Any ! Really can't handle this !"); + OSL_ENSURE(_pPointerToMember, + "OPropertyContainerHelper::registerProperty: you gave me nonsense : the pointer must be non-NULL"); + + PropertyDescription aNewProp; + aNewProp.aProperty = Property( _rName, _nHandle, _rMemberType, static_cast<sal_Int16>(_nAttributes) ); + aNewProp.eLocated = PropertyDescription::LocationType::DerivedClassRealType; + aNewProp.aLocation.pDerivedClassMember = _pPointerToMember; + + implPushBackProperty(aNewProp); +} + + +void OPropertyContainerHelper::revokeProperty( sal_Int32 _nHandle ) +{ + PropertiesIterator aPos = searchHandle( _nHandle ); + if ( aPos == m_aProperties.end() ) + throw UnknownPropertyException(OUString::number(_nHandle)); + m_aProperties.erase( aPos ); +} + + +void OPropertyContainerHelper::registerMayBeVoidProperty(const OUString& _rName, sal_Int32 _nHandle, sal_Int32 _nAttributes, + Any* _pPointerToMember, const Type& _rExpectedType) +{ + OSL_ENSURE((_nAttributes & PropertyAttribute::MAYBEVOID) != 0, + "OPropertyContainerHelper::registerMayBeVoidProperty: why calling this when the attributes say nothing about may-be-void ?"); + OSL_ENSURE(!_rExpectedType.equals(cppu::UnoType<Any>::get()), + "OPropertyContainerHelper::registerMayBeVoidProperty: don't give my the type of a uno::Any ! Really can't handle this !"); + OSL_ENSURE(_pPointerToMember, + "OPropertyContainerHelper::registerMayBeVoidProperty: you gave me nonsense : the pointer must be non-NULL"); + + _nAttributes |= PropertyAttribute::MAYBEVOID; + + PropertyDescription aNewProp; + aNewProp.aProperty = Property( _rName, _nHandle, _rExpectedType, static_cast<sal_Int16>(_nAttributes) ); + aNewProp.eLocated = PropertyDescription::LocationType::DerivedClassAnyType; + aNewProp.aLocation.pDerivedClassMember = _pPointerToMember; + + implPushBackProperty(aNewProp); +} + + +void OPropertyContainerHelper::registerPropertyNoMember(const OUString& _rName, sal_Int32 _nHandle, sal_Int32 _nAttributes, + const Type& _rType, css::uno::Any const & _pInitialValue) +{ + OSL_ENSURE(!_rType.equals(cppu::UnoType<Any>::get()), + "OPropertyContainerHelper::registerPropertyNoMember : don't give my the type of a uno::Any ! Really can't handle this !"); + OSL_ENSURE( + (_pInitialValue.isExtractableTo(_rType) + || (!_pInitialValue.hasValue() + && (_nAttributes & PropertyAttribute::MAYBEVOID) != 0)), + "bad initial value"); + + PropertyDescription aNewProp; + aNewProp.aProperty = Property( _rName, _nHandle, _rType, static_cast<sal_Int16>(_nAttributes) ); + aNewProp.eLocated = PropertyDescription::LocationType::HoldMyself; + aNewProp.aLocation.nOwnClassVectorIndex = m_aHoldProperties.size(); + m_aHoldProperties.push_back(_pInitialValue); + + implPushBackProperty(aNewProp); +} + + +bool OPropertyContainerHelper::isRegisteredProperty( sal_Int32 _nHandle ) const +{ + return const_cast< OPropertyContainerHelper* >( this )->searchHandle( _nHandle ) != m_aProperties.end(); +} + + +bool OPropertyContainerHelper::isRegisteredProperty( const OUString& _rName ) const +{ + // TODO: the current structure is from a time where properties were + // static, not dynamic. Since we allow that properties are also dynamic, + // i.e. registered and revoked even though the XPropertySet has already been + // accessed, a vector is not really the best data structure anymore ... + + return std::any_of( + m_aProperties.begin(), + m_aProperties.end(), + PropertyDescriptionNameMatch( _rName ) + ); +} + + +namespace +{ + struct ComparePropertyHandles + { + bool operator()( const PropertyDescription& _rLHS, const PropertyDescription& _nRHS ) const + { + return _rLHS.aProperty.Handle < _nRHS.aProperty.Handle; + } + }; +} + + +void OPropertyContainerHelper::implPushBackProperty(const PropertyDescription& _rProp) +{ +#ifdef DBG_UTIL + for (const auto& checkConflicts : m_aProperties) + { + OSL_ENSURE(checkConflicts.aProperty.Name != _rProp.aProperty.Name, "OPropertyContainerHelper::implPushBackProperty: name already exists!"); + OSL_ENSURE(checkConflicts.aProperty.Handle != _rProp.aProperty.Handle, "OPropertyContainerHelper::implPushBackProperty: handle already exists!"); + } +#endif + + PropertiesIterator pos = std::lower_bound( + m_aProperties.begin(), m_aProperties.end(), + _rProp, ComparePropertyHandles() ); + + m_aProperties.insert( pos, _rProp ); +} + + +namespace +{ + void lcl_throwIllegalPropertyValueTypeException( const PropertyDescription& _rProperty, const Any& _rValue ) + { + throw IllegalArgumentException( + "The given value cannot be converted to the required property type." + " (property name \"" + _rProperty.aProperty.Name + + "\", found value type \"" + _rValue.getValueType().getTypeName() + + "\", required property type \"" + _rProperty.aProperty.Type.getTypeName() + + "\")", + nullptr, 4 ); + } +} + + +bool OPropertyContainerHelper::convertFastPropertyValue( + Any& _rConvertedValue, Any& _rOldValue, sal_Int32 _nHandle, const Any& _rValue ) +{ + bool bModified = false; + + // get the property somebody is asking for + PropertiesIterator aPos = searchHandle(_nHandle); + if (aPos == m_aProperties.end()) + { + OSL_FAIL( "OPropertyContainerHelper::convertFastPropertyValue: unknown handle!" ); + // should not happen if the derived class has built a correct property set info helper to be used by + // our base class OPropertySetHelper + return bModified; + } + + switch (aPos->eLocated) + { + // similar handling for the two cases where the value is stored in an any + case PropertyDescription::LocationType::HoldMyself: + case PropertyDescription::LocationType::DerivedClassAnyType: + { + bool bMayBeVoid = ((aPos->aProperty.Attributes & PropertyAttribute::MAYBEVOID) != 0); + + + // non modifiable version of the value-to-be-set + Any aNewRequestedValue( _rValue ); + + // normalization + // #i29490# + if ( !aNewRequestedValue.getValueType().equals( aPos->aProperty.Type ) ) + { // the actually given value is not of the same type as the one required + Any aProperlyTyped( nullptr, aPos->aProperty.Type.getTypeLibType() ); + + if ( uno_type_assignData( + const_cast< void* >( aProperlyTyped.getValue() ), aProperlyTyped.getValueType().getTypeLibType(), + const_cast< void* >( aNewRequestedValue.getValue() ), aNewRequestedValue.getValueType().getTypeLibType(), + reinterpret_cast< uno_QueryInterfaceFunc >( cpp_queryInterface ), + reinterpret_cast< uno_AcquireFunc >( cpp_acquire ), + reinterpret_cast< uno_ReleaseFunc >( cpp_release ) + ) + ) + { + // we were able to query the given XInterface-derivee for the interface + // which is required for this property + aNewRequestedValue = aProperlyTyped; + } + } + + // argument check + if ( ! ( (bMayBeVoid && !aNewRequestedValue.hasValue()) // void is allowed if the attribute says so + || (aNewRequestedValue.getValueType().equals(aPos->aProperty.Type)) // else the types have to be equal + ) + ) + { + lcl_throwIllegalPropertyValueTypeException( *aPos, _rValue ); + } + + Any* pPropContainer = nullptr; + // the pointer to the any which holds the property value, no matter if located in the derived class + // or in out vector + + if (PropertyDescription::LocationType::HoldMyself == aPos->eLocated) + { + OSL_ENSURE(aPos->aLocation.nOwnClassVectorIndex < m_aHoldProperties.size(), + "OPropertyContainerHelper::convertFastPropertyValue: invalid position !"); + auto aIter = m_aHoldProperties.begin() + aPos->aLocation.nOwnClassVectorIndex; + pPropContainer = &(*aIter); + } + else + pPropContainer = static_cast<Any*>(aPos->aLocation.pDerivedClassMember); + + // check if the new value differs from the current one + if (!pPropContainer->hasValue() || !aNewRequestedValue.hasValue()) + bModified = pPropContainer->hasValue() != aNewRequestedValue.hasValue(); + else + bModified = !uno_type_equalData( + const_cast< void* >( pPropContainer->getValue() ), aPos->aProperty.Type.getTypeLibType(), + const_cast< void* >( aNewRequestedValue.getValue() ), aPos->aProperty.Type.getTypeLibType(), + reinterpret_cast< uno_QueryInterfaceFunc >( cpp_queryInterface ), + reinterpret_cast< uno_ReleaseFunc >( cpp_release ) + ); + + if (bModified) + { + _rOldValue = *pPropContainer; + _rConvertedValue = aNewRequestedValue; + } + } + break; + case PropertyDescription::LocationType::DerivedClassRealType: + // let the UNO runtime library do any possible conversion + // this may include a change of the type - for instance, if a LONG is required, + // but a short is given, then this is valid, as it can be converted without any potential + // data loss + + Any aProperlyTyped; + const Any* pNewValue = &_rValue; + + if (!_rValue.getValueType().equals(aPos->aProperty.Type)) + { + bool bConverted = false; + + // a temporary any of the correct (required) type + aProperlyTyped = Any( nullptr, aPos->aProperty.Type.getTypeLibType() ); + // (need this as we do not want to overwrite the derived class member here) + + if ( uno_type_assignData( + const_cast<void*>(aProperlyTyped.getValue()), aProperlyTyped.getValueType().getTypeLibType(), + const_cast<void*>(_rValue.getValue()), _rValue.getValueType().getTypeLibType(), + reinterpret_cast< uno_QueryInterfaceFunc >( cpp_queryInterface ), + reinterpret_cast< uno_AcquireFunc >( cpp_acquire ), + reinterpret_cast< uno_ReleaseFunc >( cpp_release ) + ) + ) + { + // could query for the requested interface + bConverted = true; + pNewValue = &aProperlyTyped; + } + + if ( !bConverted ) + lcl_throwIllegalPropertyValueTypeException( *aPos, _rValue ); + } + + // from here on, we should have the proper type + OSL_ENSURE( pNewValue->getValueType() == aPos->aProperty.Type, + "OPropertyContainerHelper::convertFastPropertyValue: conversion failed!" ); + bModified = !uno_type_equalData( + aPos->aLocation.pDerivedClassMember, aPos->aProperty.Type.getTypeLibType(), + const_cast<void*>(pNewValue->getValue()), aPos->aProperty.Type.getTypeLibType(), + reinterpret_cast< uno_QueryInterfaceFunc >( cpp_queryInterface ), + reinterpret_cast< uno_ReleaseFunc >( cpp_release ) + ); + + if (bModified) + { + _rOldValue.setValue(aPos->aLocation.pDerivedClassMember, aPos->aProperty.Type); + _rConvertedValue = *pNewValue; + } + break; + } + + return bModified; +} + + +void OPropertyContainerHelper::setFastPropertyValue(sal_Int32 _nHandle, const Any& _rValue) +{ + // get the property somebody is asking for + PropertiesIterator aPos = searchHandle(_nHandle); + if (aPos == m_aProperties.end()) + { + OSL_FAIL( "OPropertyContainerHelper::setFastPropertyValue: unknown handle!" ); + // should not happen if the derived class has built a correct property set info helper to be used by + // our base class OPropertySetHelper + return; + } + + bool bSuccess = true; + + switch (aPos->eLocated) + { + case PropertyDescription::LocationType::HoldMyself: + m_aHoldProperties[aPos->aLocation.nOwnClassVectorIndex] = _rValue; + break; + + case PropertyDescription::LocationType::DerivedClassAnyType: + *static_cast< Any* >(aPos->aLocation.pDerivedClassMember) = _rValue; + break; + + case PropertyDescription::LocationType::DerivedClassRealType: + // copy the data from the to-be-set value + bSuccess = uno_type_assignData( + aPos->aLocation.pDerivedClassMember, aPos->aProperty.Type.getTypeLibType(), + const_cast< void* >( _rValue.getValue() ), _rValue.getValueType().getTypeLibType(), + reinterpret_cast< uno_QueryInterfaceFunc >( cpp_queryInterface ), + reinterpret_cast< uno_AcquireFunc >( cpp_acquire ), + reinterpret_cast< uno_ReleaseFunc >( cpp_release ) ); + + OSL_ENSURE( bSuccess, + "OPropertyContainerHelper::setFastPropertyValue: ooops... the value could not be assigned!"); + + break; + } +} + +void OPropertyContainerHelper::getFastPropertyValue(Any& _rValue, sal_Int32 _nHandle) const +{ + // get the property somebody is asking for + PropertiesIterator aPos = const_cast<OPropertyContainerHelper*>(this)->searchHandle(_nHandle); + if (aPos == m_aProperties.end()) + { + OSL_FAIL( "OPropertyContainerHelper::getFastPropertyValue: unknown handle!" ); + // should not happen if the derived class has built a correct property set info helper to be used by + // our base class OPropertySetHelper + return; + } + + switch (aPos->eLocated) + { + case PropertyDescription::LocationType::HoldMyself: + OSL_ENSURE(aPos->aLocation.nOwnClassVectorIndex < m_aHoldProperties.size(), + "OPropertyContainerHelper::convertFastPropertyValue: invalid position !"); + _rValue = m_aHoldProperties[aPos->aLocation.nOwnClassVectorIndex]; + break; + case PropertyDescription::LocationType::DerivedClassAnyType: + _rValue = *static_cast<Any*>(aPos->aLocation.pDerivedClassMember); + break; + case PropertyDescription::LocationType::DerivedClassRealType: + _rValue.setValue(aPos->aLocation.pDerivedClassMember, aPos->aProperty.Type); + break; + } +} + + +OPropertyContainerHelper::PropertiesIterator OPropertyContainerHelper::searchHandle(sal_Int32 _nHandle) +{ + PropertyDescription aHandlePropDesc; + aHandlePropDesc.aProperty.Handle = _nHandle; + // search a lower bound + PropertiesIterator aLowerBound = std::lower_bound( + m_aProperties.begin(), + m_aProperties.end(), + aHandlePropDesc, + PropertyDescriptionHandleCompare()); + + // check for identity + if ((aLowerBound != m_aProperties.end()) && aLowerBound->aProperty.Handle != _nHandle) + aLowerBound = m_aProperties.end(); + + return aLowerBound; +} + + +const Property& OPropertyContainerHelper::getProperty( const OUString& _rName ) const +{ + ConstPropertiesIterator pos = std::find_if( + m_aProperties.begin(), + m_aProperties.end(), + PropertyDescriptionNameMatch( _rName ) + ); + if ( pos == m_aProperties.end() ) + throw UnknownPropertyException( _rName ); + + return pos->aProperty; +} + + +void OPropertyContainerHelper::describeProperties(Sequence< Property >& _rProps) const +{ + Sequence< Property > aOwnProps(m_aProperties.size()); + Property* pOwnProps = aOwnProps.getArray(); + + for (const auto& rProp : m_aProperties) + { + pOwnProps->Name = rProp.aProperty.Name; + pOwnProps->Handle = rProp.aProperty.Handle; + pOwnProps->Attributes = rProp.aProperty.Attributes; + pOwnProps->Type = rProp.aProperty.Type; + ++pOwnProps; + } + + // as our property vector is sorted by handles, not by name, we have to sort aOwnProps + auto [begin, end] = asNonConstRange(aOwnProps); + std::sort(begin, end, PropertyCompareByName()); + + // unfortunately the STL merge function does not allow the output range to overlap one of the input ranges, + // so we need an extra sequence + Sequence< Property > aOutput(_rProps.getLength() + aOwnProps.getLength()); + // do the merge + std::merge( std::cbegin(_rProps), std::cend(_rProps), // input 1 + std::cbegin(aOwnProps), std::cend(aOwnProps), // input 2 + aOutput.getArray(), // output + PropertyCompareByName() // compare operator + ); + + // copy the output + _rProps = aOutput; +} + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/propertysethelper.cxx b/comphelper/source/property/propertysethelper.cxx new file mode 100644 index 000000000..519b0705f --- /dev/null +++ b/comphelper/source/property/propertysethelper.cxx @@ -0,0 +1,271 @@ +/* -*- 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/propertysetinfo.hxx> +#include <comphelper/propertysethelper.hxx> +#include <osl/diagnose.h> +#include <rtl/ref.hxx> + +#include <memory> +#include <utility> + +using namespace ::comphelper; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; + +static PropertyMapEntry const * find( const rtl::Reference<PropertySetInfo>& mxInfo, const OUString& aName ) noexcept +{ + PropertyMap::const_iterator aIter = mxInfo->getPropertyMap().find( aName ); + + if( mxInfo->getPropertyMap().end() != aIter ) + return (*aIter).second; + else + return nullptr; +} + + +PropertySetHelper::PropertySetHelper( rtl::Reference<comphelper::PropertySetInfo> xInfo ) noexcept + : mxInfo(std::move(xInfo)) +{ +} + +PropertySetHelper::~PropertySetHelper() noexcept +{ +} + +// XPropertySet +Reference< XPropertySetInfo > SAL_CALL PropertySetHelper::getPropertySetInfo( ) +{ + return mxInfo; +} + +void SAL_CALL PropertySetHelper::setPropertyValue( const OUString& aPropertyName, const Any& aValue ) +{ + PropertyMapEntry const * aEntries[2]; + aEntries[0] = find( mxInfo, aPropertyName ); + + if( nullptr == aEntries[0] ) + throw UnknownPropertyException( aPropertyName, static_cast< XPropertySet* >( this ) ); + + aEntries[1] = nullptr; + + _setPropertyValues( aEntries, &aValue ); +} + +Any SAL_CALL PropertySetHelper::getPropertyValue( const OUString& PropertyName ) +{ + PropertyMapEntry const * aEntries[2]; + aEntries[0] = find( mxInfo, PropertyName ); + + if( nullptr == aEntries[0] ) + throw UnknownPropertyException( PropertyName, static_cast< XPropertySet* >( this ) ); + + aEntries[1] = nullptr; + + Any aAny; + _getPropertyValues( aEntries, &aAny ); + + return aAny; +} + +void SAL_CALL PropertySetHelper::addPropertyChangeListener( const OUString&, const Reference< XPropertyChangeListener >& ) +{ + // todo +} + +void SAL_CALL PropertySetHelper::removePropertyChangeListener( const OUString&, const Reference< XPropertyChangeListener >& ) +{ + // todo +} + +void SAL_CALL PropertySetHelper::addVetoableChangeListener( const OUString&, const Reference< XVetoableChangeListener >& ) +{ + // todo +} + +void SAL_CALL PropertySetHelper::removeVetoableChangeListener( const OUString&, const Reference< XVetoableChangeListener >& ) +{ + // todo +} + +// XMultiPropertySet +void SAL_CALL PropertySetHelper::setPropertyValues( const Sequence< OUString >& rPropertyNames, const Sequence< Any >& rValues ) +{ + const sal_Int32 nCount = rPropertyNames.getLength(); + + if( nCount != rValues.getLength() ) + throw IllegalArgumentException("lengths do not match", uno::Reference<uno::XInterface>(), -1); + + if( !nCount ) + return; + + std::unique_ptr<PropertyMapEntry const *[]> pEntries(new PropertyMapEntry const *[nCount+1]); + pEntries[nCount] = nullptr; + const OUString* pNames = rPropertyNames.getConstArray(); + + bool bUnknown = false; + sal_Int32 n; + for( n = 0; !bUnknown && ( n < nCount ); n++, pNames++ ) + { + pEntries[n] = find( mxInfo, *pNames ); + bUnknown = nullptr == pEntries[n]; + } + + if( !bUnknown ) + _setPropertyValues( pEntries.get(), rValues.getConstArray() ); + + if( bUnknown ) + throw RuntimeException( *pNames, static_cast< XPropertySet* >( this ) ); +} + +Sequence< Any > SAL_CALL PropertySetHelper::getPropertyValues(const Sequence< OUString >& rPropertyNames) +{ + const sal_Int32 nCount = rPropertyNames.getLength(); + + if( !nCount ) + return Sequence< Any >(); + + std::unique_ptr<PropertyMapEntry const *[]> pEntries(new PropertyMapEntry const *[nCount+1]); + const OUString* pNames = rPropertyNames.getConstArray(); + + bool bUnknown = false; + sal_Int32 n; + for( n = 0; !bUnknown && ( n < nCount ); n++, pNames++ ) + { + pEntries[n] = find( mxInfo, *pNames ); + bUnknown = nullptr == pEntries[n]; + } + + if( bUnknown ) + throw RuntimeException( *pNames, static_cast< XPropertySet* >( this ) ); + + pEntries[nCount] = nullptr; + Sequence< Any > aValues(nCount); + aValues.realloc(nCount); + _getPropertyValues( pEntries.get(), aValues.getArray() ); + return aValues; + +} + +void SAL_CALL PropertySetHelper::addPropertiesChangeListener( const Sequence< OUString >&, const Reference< XPropertiesChangeListener >& ) +{ + // todo +} + +void SAL_CALL PropertySetHelper::removePropertiesChangeListener( const Reference< XPropertiesChangeListener >& ) +{ + // todo +} + +void SAL_CALL PropertySetHelper::firePropertiesChangeEvent( const Sequence< OUString >&, const Reference< XPropertiesChangeListener >& ) +{ + // todo +} + +// XPropertyState +PropertyState SAL_CALL PropertySetHelper::getPropertyState( const OUString& PropertyName ) +{ + PropertyMapEntry const * aEntries[2]; + + aEntries[0] = find( mxInfo, PropertyName ); + if( aEntries[0] == nullptr ) + throw UnknownPropertyException( PropertyName, static_cast< XPropertySet* >( this ) ); + + aEntries[1] = nullptr; + + PropertyState aState(PropertyState_AMBIGUOUS_VALUE); + _getPropertyStates( aEntries, &aState ); + + return aState; +} + +Sequence< PropertyState > SAL_CALL PropertySetHelper::getPropertyStates( const Sequence< OUString >& aPropertyName ) +{ + const sal_Int32 nCount = aPropertyName.getLength(); + + Sequence< PropertyState > aStates( nCount ); + + if( nCount ) + { + const OUString* pNames = aPropertyName.getConstArray(); + + bool bUnknown = false; + + std::unique_ptr<PropertyMapEntry const *[]> pEntries(new PropertyMapEntry const *[nCount+1]); + + sal_Int32 n; + for( n = 0; !bUnknown && (n < nCount); n++, pNames++ ) + { + pEntries[n] = find( mxInfo, *pNames ); + bUnknown = nullptr == pEntries[n]; + } + + pEntries[nCount] = nullptr; + + if( !bUnknown ) + _getPropertyStates( pEntries.get(), aStates.getArray() ); + + if( bUnknown ) + throw UnknownPropertyException( *pNames, static_cast< XPropertySet* >( this ) ); + } + + return aStates; +} + +void SAL_CALL PropertySetHelper::setPropertyToDefault( const OUString& PropertyName ) +{ + PropertyMapEntry const *pEntry = find(mxInfo, PropertyName ); + if( nullptr == pEntry ) + throw UnknownPropertyException( PropertyName, static_cast< XPropertySet* >( this ) ); + + _setPropertyToDefault( pEntry ); +} + +Any SAL_CALL PropertySetHelper::getPropertyDefault( const OUString& aPropertyName ) +{ + PropertyMapEntry const * pEntry = find(mxInfo, aPropertyName ); + if( nullptr == pEntry ) + throw UnknownPropertyException( aPropertyName, static_cast< XPropertySet* >( this ) ); + + return _getPropertyDefault( pEntry ); +} + +void PropertySetHelper::_getPropertyStates( + const comphelper::PropertyMapEntry**, PropertyState*) +{ + OSL_FAIL( "you have to implement this yourself!"); +} + +void +PropertySetHelper::_setPropertyToDefault(const comphelper::PropertyMapEntry*) +{ + OSL_FAIL( "you have to implement this yourself!"); +} + +Any PropertySetHelper::_getPropertyDefault(const comphelper::PropertyMapEntry*) +{ + OSL_FAIL( "you have to implement this yourself!"); + + Any aAny; + return aAny; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/propertysetinfo.cxx b/comphelper/source/property/propertysetinfo.cxx new file mode 100644 index 000000000..206129c5f --- /dev/null +++ b/comphelper/source/property/propertysetinfo.cxx @@ -0,0 +1,120 @@ +/* -*- 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/propertysetinfo.hxx> +#include <comphelper/sequence.hxx> + + +using namespace ::comphelper; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; + +PropertySetInfo::PropertySetInfo() noexcept +{ +} + +PropertySetInfo::PropertySetInfo( o3tl::span<const PropertyMapEntry> pMap ) noexcept +{ + maPropertyMap.reserve(pMap.size()); + for (const auto & rEntry : pMap) + { + // check for duplicates + assert(maPropertyMap.find(rEntry.maName) == maPropertyMap.end()); + // Make sure there are no accidental empty entries left at the end of the array from + // when this method used to take a empty-terminated array. + assert(!rEntry.maName.isEmpty()); + + maPropertyMap.emplace(rEntry.maName, &rEntry); + } +} + +PropertySetInfo::~PropertySetInfo() noexcept +{ +} + +void PropertySetInfo::add( o3tl::span<PropertyMapEntry const> pMap ) noexcept +{ + maPropertyMap.reserve(maPropertyMap.size() + pMap.size()); + for (const auto & rEntry : pMap) + { + // check for duplicates + assert(maPropertyMap.find(rEntry.maName) == maPropertyMap.end()); + // Make sure there are no accidental empty entries left at the end of the array from + // when this method used to take a empty-terminated array. + assert(!rEntry.maName.isEmpty()); + + maPropertyMap.emplace(rEntry.maName, &rEntry); + } + + // clear cache + maProperties.realloc(0); +} + +void PropertySetInfo::remove( const OUString& aName ) noexcept +{ + maPropertyMap.erase( aName ); + maProperties.realloc(0); +} + +Sequence< css::beans::Property > SAL_CALL PropertySetInfo::getProperties() +{ + // maybe we have to generate the properties after + // a change in the property map or at first call + // to getProperties + if( maProperties.size() != maPropertyMap.size() ) + { + maProperties.realloc( maPropertyMap.size() ); + auto propIter = maProperties.getArray(); + + for( const auto& rProperty : maPropertyMap ) + { + PropertyMapEntry const * pEntry = rProperty.second; + + propIter->Name = pEntry->maName; + propIter->Handle = pEntry->mnHandle; + propIter->Type = pEntry->maType; + propIter->Attributes = pEntry->mnAttributes; + + ++propIter; + } + } + return maProperties; +} + +Property SAL_CALL PropertySetInfo::getPropertyByName( const OUString& aName ) +{ + PropertyMap::iterator aIter = maPropertyMap.find( aName ); + + if( maPropertyMap.end() == aIter ) + throw UnknownPropertyException( aName ); + + PropertyMapEntry const * pEntry = (*aIter).second; + + return Property( aName, pEntry->mnHandle, pEntry->maType, pEntry->mnAttributes ); +} + +sal_Bool SAL_CALL PropertySetInfo::hasPropertyByName( const OUString& aName ) +{ + return maPropertyMap.find( aName ) != maPropertyMap.end(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/propertystatecontainer.cxx b/comphelper/source/property/propertystatecontainer.cxx new file mode 100644 index 000000000..e19e78733 --- /dev/null +++ b/comphelper/source/property/propertystatecontainer.cxx @@ -0,0 +1,183 @@ +/* -*- 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 <string_view> + +#include <comphelper/propertystatecontainer.hxx> + + +namespace comphelper +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::lang; + + namespace + { + OUString lcl_getUnknownPropertyErrorMessage( std::u16string_view _rPropertyName ) + { + // TODO: perhaps it's time to think about resources in the comphelper module? + // Would be nice to have localized exception strings (a simply resource file containing + // strings only would suffice, and could be realized with a UNO service, so we do not + // need the dependency to the Tools project) + return OUString::Concat("The property \"") + _rPropertyName + "\" is unknown."; + } + } + + OPropertyStateContainer::OPropertyStateContainer( ::cppu::OBroadcastHelper& _rBHelper ) + :OPropertyContainer( _rBHelper ) + { + } + + + Any SAL_CALL OPropertyStateContainer::queryInterface( const Type& _rType ) + { + Any aReturn = OPropertyContainer::queryInterface( _rType ); + if ( !aReturn.hasValue() ) + aReturn = OPropertyStateContainer_TBase::queryInterface( _rType ); + return aReturn; + } + + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OPropertyStateContainer, OPropertyContainer, OPropertyStateContainer_TBase ) + + + sal_Int32 OPropertyStateContainer::getHandleForName( const OUString& _rPropertyName ) + { + // look up the handle for the name + ::cppu::IPropertyArrayHelper& rPH = getInfoHelper(); + sal_Int32 nHandle = rPH.getHandleByName( _rPropertyName ); + + if ( -1 == nHandle ) + throw UnknownPropertyException( lcl_getUnknownPropertyErrorMessage( _rPropertyName ), static_cast< XPropertyState* >( this ) ); + + return nHandle; + } + + + PropertyState SAL_CALL OPropertyStateContainer::getPropertyState( const OUString& _rPropertyName ) + { + return getPropertyStateByHandle( getHandleForName( _rPropertyName ) ); + } + + + Sequence< PropertyState > SAL_CALL OPropertyStateContainer::getPropertyStates( const Sequence< OUString >& _rPropertyNames ) + { + sal_Int32 nProperties = _rPropertyNames.getLength(); + Sequence< PropertyState> aStates( nProperties ); + if ( !nProperties ) + return aStates; + +#ifdef DBG_UTIL + // precondition: property sequence is sorted (the algorithm below relies on this) + { + const OUString* pNames = _rPropertyNames.getConstArray(); + const OUString* pNamesCompare = pNames + 1; + const OUString* pNamesEnd = _rPropertyNames.getConstArray() + _rPropertyNames.getLength(); + for ( ; pNamesCompare != pNamesEnd; ++pNames, ++pNamesCompare ) + OSL_PRECOND( pNames->compareTo( *pNamesCompare ) < 0, + "OPropertyStateContainer::getPropertyStates: property sequence not sorted!" ); + } +#endif + + const OUString* pLookup = _rPropertyNames.getConstArray(); + const OUString* pLookupEnd = pLookup + nProperties; + PropertyState* pStates = aStates.getArray(); + + cppu::IPropertyArrayHelper& rHelper = getInfoHelper(); + Sequence< Property> aAllProperties = rHelper.getProperties(); + sal_Int32 nAllProperties = aAllProperties.getLength(); + const Property* pAllProperties = aAllProperties.getConstArray(); + const Property* pAllPropertiesEnd = pAllProperties + nAllProperties; + + osl::MutexGuard aGuard( rBHelper.rMutex ); + for ( ; ( pAllProperties != pAllPropertiesEnd ) && ( pLookup != pLookupEnd ); ++pAllProperties ) + { +#ifdef DBG_UTIL + if ( pAllProperties < pAllPropertiesEnd - 1 ) + OSL_ENSURE( pAllProperties->Name.compareTo( (pAllProperties + 1)->Name ) < 0, + "OPropertyStateContainer::getPropertyStates: all-properties not sorted!" ); +#endif + if ( pAllProperties->Name == *pLookup ) + { + *pStates++ = getPropertyState( *pLookup ); + ++pLookup; + } + } + + if ( pLookup != pLookupEnd ) + // we run out of properties from the IPropertyArrayHelper, but still have properties to lookup + // -> we were asked for a nonexistent property + throw UnknownPropertyException( lcl_getUnknownPropertyErrorMessage( *pLookup ), static_cast< XPropertyState* >( this ) ); + + return aStates; + } + + + void SAL_CALL OPropertyStateContainer::setPropertyToDefault( const OUString& _rPropertyName ) + { + setPropertyToDefaultByHandle( getHandleForName( _rPropertyName ) ); + } + + + Any SAL_CALL OPropertyStateContainer::getPropertyDefault( const OUString& _rPropertyName ) + { + Any aDefault; + getPropertyDefaultByHandle( getHandleForName( _rPropertyName ), aDefault ); + return aDefault; + } + + + PropertyState OPropertyStateContainer::getPropertyStateByHandle( sal_Int32 _nHandle ) const + { + // simply compare the current and the default value + Any aCurrentValue; + getFastPropertyValue( aCurrentValue, _nHandle ); + Any aDefaultValue; + getPropertyDefaultByHandle( _nHandle, aDefaultValue ); + + bool bEqual = uno_type_equalData( + const_cast< void* >( aCurrentValue.getValue() ), aCurrentValue.getValueType().getTypeLibType(), + const_cast< void* >( aDefaultValue.getValue() ), aDefaultValue.getValueType().getTypeLibType(), + reinterpret_cast< uno_QueryInterfaceFunc >(cpp_queryInterface), + reinterpret_cast< uno_ReleaseFunc >(cpp_release) + ); + if ( bEqual ) + return PropertyState_DEFAULT_VALUE; + else + return PropertyState_DIRECT_VALUE; + } + + + void OPropertyStateContainer::setPropertyToDefaultByHandle( sal_Int32 _nHandle ) + { + Any aDefault; + getPropertyDefaultByHandle( _nHandle, aDefault ); + setFastPropertyValue( _nHandle, aDefault ); + } + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/propmultiplex.cxx b/comphelper/source/property/propmultiplex.cxx new file mode 100644 index 000000000..66a1545f8 --- /dev/null +++ b/comphelper/source/property/propmultiplex.cxx @@ -0,0 +1,153 @@ +/* -*- 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/beans/XPropertySet.hpp> +#include <comphelper/propmultiplex.hxx> +#include <osl/diagnose.h> + + +namespace comphelper +{ + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; + +OPropertyChangeListener::~OPropertyChangeListener() +{ + if (m_xAdapter.is()) + m_xAdapter->dispose(); +} + + +void OPropertyChangeListener::_disposing(const EventObject&) +{ + // nothing to do here +} + + +void OPropertyChangeListener::disposeAdapter() +{ + if ( m_xAdapter.is() ) + m_xAdapter->dispose(); + + // will automatically set a new adapter + OSL_ENSURE( !m_xAdapter.is(), "OPropertyChangeListener::disposeAdapter: what did dispose do?" ); +} + + +void OPropertyChangeListener::setAdapter(OPropertyChangeMultiplexer* pAdapter) +{ + ::osl::MutexGuard aGuard(m_rMutex); + m_xAdapter = pAdapter; +} + +OPropertyChangeMultiplexer::OPropertyChangeMultiplexer(OPropertyChangeListener* _pListener, const Reference< XPropertySet>& _rxSet, bool _bAutoReleaseSet) + :m_xSet(_rxSet) + ,m_pListener(_pListener) + ,m_nLockCount(0) + ,m_bListening(false) + ,m_bAutoSetRelease(_bAutoReleaseSet) +{ + m_pListener->setAdapter(this); +} + + +OPropertyChangeMultiplexer::~OPropertyChangeMultiplexer() +{ +} + + +void OPropertyChangeMultiplexer::lock() +{ + ++m_nLockCount; +} + + +void OPropertyChangeMultiplexer::unlock() +{ + --m_nLockCount; +} + + +void OPropertyChangeMultiplexer::dispose() +{ + if (!m_bListening) + return; + + Reference< XPropertyChangeListener> xPreventDelete(this); + + for (const OUString& rProp : m_aProperties) + m_xSet->removePropertyChangeListener(rProp, static_cast< XPropertyChangeListener*>(this)); + + m_pListener->setAdapter(nullptr); + + m_pListener = nullptr; + m_bListening = false; + + if (m_bAutoSetRelease) + m_xSet = nullptr; +} + +// XEventListener + +void SAL_CALL OPropertyChangeMultiplexer::disposing( const EventObject& _rSource) +{ + if (m_pListener) + { + // tell the listener + if (!locked()) + m_pListener->_disposing(_rSource); + // disconnect the listener + if (m_pListener) // may have been reset whilst calling into _disposing + m_pListener->setAdapter(nullptr); + } + + m_pListener = nullptr; + m_bListening = false; + + if (m_bAutoSetRelease) + m_xSet = nullptr; +} + +// XPropertyChangeListener + +void SAL_CALL OPropertyChangeMultiplexer::propertyChange( const PropertyChangeEvent& _rEvent ) +{ + if (m_pListener && !locked()) + m_pListener->_propertyChanged(_rEvent); +} + + +void OPropertyChangeMultiplexer::addProperty(const OUString& _sPropertyName) +{ + if (m_xSet.is()) + { + m_xSet->addPropertyChangeListener(_sPropertyName, static_cast< XPropertyChangeListener*>(this)); + m_aProperties.push_back(_sPropertyName); + m_bListening = true; + } +} + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/property/propstate.cxx b/comphelper/source/property/propstate.cxx new file mode 100644 index 000000000..183f51efc --- /dev/null +++ b/comphelper/source/property/propstate.cxx @@ -0,0 +1,228 @@ +/* -*- 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/propstate.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <comphelper/sequence.hxx> + +namespace comphelper +{ + + + using ::com::sun::star::uno::Type; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::lang::XTypeProvider; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::cpp_queryInterface; + using ::com::sun::star::uno::cpp_release; + using ::com::sun::star::beans::PropertyState_DEFAULT_VALUE; + using ::com::sun::star::beans::PropertyState_DIRECT_VALUE; + + + // OPropertyStateHelper + + + css::uno::Any SAL_CALL OPropertyStateHelper::queryInterface(const css::uno::Type& _rType) + { + css::uno::Any aReturn = OPropertySetHelper2::queryInterface(_rType); + // our own ifaces + if ( !aReturn.hasValue() ) + aReturn = ::cppu::queryInterface(_rType, static_cast< css::beans::XPropertyState*>(this)); + + return aReturn; + } + + + css::uno::Sequence<css::uno::Type> OPropertyStateHelper::getTypes() + { + return { + cppu::UnoType<css::beans::XPropertySet>::get(), + cppu::UnoType<css::beans::XMultiPropertySet>::get(), + cppu::UnoType<css::beans::XFastPropertySet>::get(), + cppu::UnoType<css::beans::XPropertySetOption>::get(), + cppu::UnoType<css::beans::XPropertyState>::get()}; + } + + OPropertyStateHelper::OPropertyStateHelper( + ::cppu::OBroadcastHelper& rBHlp, + ::cppu::IEventNotificationHook *i_pFireEvents) + : ::cppu::OPropertySetHelper2(rBHlp, i_pFireEvents) { } + + OPropertyStateHelper::~OPropertyStateHelper() {} + + + void OPropertyStateHelper::firePropertyChange(sal_Int32 nHandle, const css::uno::Any& aNewValue, const css::uno::Any& aOldValue) + { + fire(&nHandle, &aNewValue, &aOldValue, 1, false); + } + + // XPropertyState + + css::beans::PropertyState SAL_CALL OPropertyStateHelper::getPropertyState(const OUString& _rsName) + { + cppu::IPropertyArrayHelper& rPH = getInfoHelper(); + sal_Int32 nHandle = rPH.getHandleByName(_rsName); + + if (nHandle == -1) + throw css::beans::UnknownPropertyException(_rsName); + + return getPropertyStateByHandle(nHandle); + } + + + void SAL_CALL OPropertyStateHelper::setPropertyToDefault(const OUString& _rsName) + { + cppu::IPropertyArrayHelper& rPH = getInfoHelper(); + sal_Int32 nHandle = rPH.getHandleByName(_rsName); + + if (nHandle == -1) + throw css::beans::UnknownPropertyException(_rsName); + + setPropertyToDefaultByHandle(nHandle); + } + + + css::uno::Any SAL_CALL OPropertyStateHelper::getPropertyDefault(const OUString& _rsName) + { + cppu::IPropertyArrayHelper& rPH = getInfoHelper(); + sal_Int32 nHandle = rPH.getHandleByName(_rsName); + + if (nHandle == -1) + throw css::beans::UnknownPropertyException(_rsName); + + return getPropertyDefaultByHandle(nHandle); + } + + + css::uno::Sequence< css::beans::PropertyState> SAL_CALL OPropertyStateHelper::getPropertyStates(const css::uno::Sequence< OUString >& _rPropertyNames) + { + sal_Int32 nLen = _rPropertyNames.getLength(); + css::uno::Sequence< css::beans::PropertyState> aRet(nLen); + css::beans::PropertyState* pValues = aRet.getArray(); + const OUString* pNames = _rPropertyNames.getConstArray(); + + cppu::IPropertyArrayHelper& rHelper = getInfoHelper(); + + css::uno::Sequence< css::beans::Property> aProps = rHelper.getProperties(); + const css::beans::Property* pProps = aProps.getConstArray(); + sal_Int32 nPropCount = aProps.getLength(); + + osl::MutexGuard aGuard(rBHelper.rMutex); + for (sal_Int32 i=0, j=0; i<nPropCount && j<nLen; ++i, ++pProps) + { + // get the values only for valid properties + if (pProps->Name == *pNames) + { + *pValues = getPropertyState(*pNames); + ++pValues; + ++pNames; + ++j; + } + } + + return aRet; + } + + + css::beans::PropertyState OPropertyStateHelper::getPropertyStateByHandle( sal_Int32 _nHandle ) + { + // simply compare the current and the default value + Any aCurrentValue = getPropertyDefaultByHandle( _nHandle ); + Any aDefaultValue; + getFastPropertyValue( aDefaultValue, _nHandle ); + + bool bEqual = uno_type_equalData( + const_cast< void* >( aCurrentValue.getValue() ), aCurrentValue.getValueType().getTypeLibType(), + const_cast< void* >( aDefaultValue.getValue() ), aDefaultValue.getValueType().getTypeLibType(), + reinterpret_cast< uno_QueryInterfaceFunc >(cpp_queryInterface), + reinterpret_cast< uno_ReleaseFunc >(cpp_release) + ); + return bEqual ? PropertyState_DEFAULT_VALUE : PropertyState_DIRECT_VALUE; + } + + + void OPropertyStateHelper::setPropertyToDefaultByHandle( sal_Int32 _nHandle ) + { + setFastPropertyValue( _nHandle, getPropertyDefaultByHandle( _nHandle ) ); + } + + + css::uno::Any OPropertyStateHelper::getPropertyDefaultByHandle( sal_Int32 ) const + { + return css::uno::Any(); + } + + + // OStatefulPropertySet + + + OStatefulPropertySet::OStatefulPropertySet() + :OPropertyStateHelper( GetBroadcastHelper() ) + { + } + + + OStatefulPropertySet::~OStatefulPropertySet() + { + } + + + Sequence< Type > SAL_CALL OStatefulPropertySet::getTypes() + { + return concatSequences( + Sequence { + cppu::UnoType<XWeak>::get(), + cppu::UnoType<XTypeProvider>::get() }, + OPropertyStateHelper::getTypes() + ); + } + + Sequence< sal_Int8 > SAL_CALL OStatefulPropertySet::getImplementationId() + { + return css::uno::Sequence<sal_Int8>(); + } + + + Any SAL_CALL OStatefulPropertySet::queryInterface( const Type& _rType ) + { + Any aReturn = OWeakObject::queryInterface( _rType ); + if ( !aReturn.hasValue() ) + aReturn = ::cppu::queryInterface( _rType, static_cast< XTypeProvider* >( this ) ); + if ( !aReturn.hasValue() ) + aReturn = OPropertyStateHelper::queryInterface( _rType ); + return aReturn; + } + + + void SAL_CALL OStatefulPropertySet::acquire() noexcept + { + ::cppu::OWeakObject::acquire(); + } + + + void SAL_CALL OStatefulPropertySet::release() noexcept + { + ::cppu::OWeakObject::release(); + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/streaming/basicio.cxx b/comphelper/source/streaming/basicio.cxx new file mode 100644 index 000000000..b8c0c96e2 --- /dev/null +++ b/comphelper/source/streaming/basicio.cxx @@ -0,0 +1,182 @@ +/* -*- 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/basicio.hxx> +#include <comphelper/bytereader.hxx> +#include <comphelper/servicehelper.hxx> +#include <com/sun/star/awt/FontDescriptor.hpp> + +namespace comphelper +{ + + +const css::uno::Reference<css::io::XObjectOutputStream>& operator << ( + const css::uno::Reference<css::io::XObjectOutputStream>& _rxOutStream, + const css::awt::FontDescriptor& _rFont) +{ + _rxOutStream->writeUTF( _rFont.Name ); + _rxOutStream->writeShort( _rFont.Height ); + _rxOutStream->writeShort( _rFont.Width ); + _rxOutStream->writeUTF( _rFont.StyleName ); + _rxOutStream->writeShort( _rFont.Family ); + _rxOutStream->writeShort( _rFont.CharSet ); + _rxOutStream->writeShort( _rFont.Pitch ); + _rxOutStream->writeDouble( _rFont.CharacterWidth ); + _rxOutStream->writeDouble( _rFont.Weight ); + _rxOutStream->writeShort( static_cast< sal_Int16 >(_rFont.Slant) ); + _rxOutStream->writeShort( _rFont.Underline ); + _rxOutStream->writeShort( _rFont.Strikeout ); + _rxOutStream->writeDouble( _rFont.Orientation ); + _rxOutStream->writeBoolean( _rFont.Kerning ); + _rxOutStream->writeBoolean( _rFont.WordLineMode ); + _rxOutStream->writeShort( _rFont.Type ); + return _rxOutStream; +} + +// FontDescriptor + +const css::uno::Reference<css::io::XObjectInputStream>& operator >> ( + const css::uno::Reference<css::io::XObjectInputStream>& _rxInStream, + css::awt::FontDescriptor& _rFont) +{ + // writing the FontDescriptor + _rFont.Name = _rxInStream->readUTF(); + _rFont.Height = _rxInStream->readShort(); + _rFont.Width = _rxInStream->readShort(); + _rFont.StyleName = _rxInStream->readUTF(); + _rFont.Family = _rxInStream->readShort(); + _rFont.CharSet = _rxInStream->readShort(); + _rFont.Pitch = _rxInStream->readShort(); + _rFont.CharacterWidth = static_cast< float >(_rxInStream->readDouble()); + _rFont.Weight = static_cast< float >(_rxInStream->readDouble()); + _rFont.Slant = static_cast<css::awt::FontSlant>(_rxInStream->readShort()); + _rFont.Underline = _rxInStream->readShort(); + _rFont.Strikeout = _rxInStream->readShort(); + _rFont.Orientation = static_cast< float >(_rxInStream->readDouble()); + _rFont.Kerning = _rxInStream->readBoolean() != 0; + _rFont.WordLineMode = _rxInStream->readBoolean() != 0; + _rFont.Type = _rxInStream->readShort(); + return _rxInStream; +} + + +const css::uno::Reference<css::io::XObjectInputStream>& operator >> (const css::uno::Reference<css::io::XObjectInputStream>& _rxInStream, bool& _rVal) +{ + _rVal = _rxInStream->readBoolean(); + return _rxInStream; +} + + +const css::uno::Reference<css::io::XObjectOutputStream>& operator << (const css::uno::Reference<css::io::XObjectOutputStream>& _rxOutStream, bool _bVal) +{ + _rxOutStream->writeBoolean(_bVal); + return _rxOutStream; +} + + +const css::uno::Reference<css::io::XObjectInputStream>& operator >> (const css::uno::Reference<css::io::XObjectInputStream>& _rxInStream, OUString& rStr) +{ + rStr = _rxInStream->readUTF(); + return _rxInStream; +} + + +const css::uno::Reference<css::io::XObjectOutputStream>& operator << (const css::uno::Reference<css::io::XObjectOutputStream>& _rxOutStream, const OUString& rStr) +{ + _rxOutStream->writeUTF(rStr); + return _rxOutStream; +} + + +const css::uno::Reference<css::io::XObjectInputStream>& operator >> (const css::uno::Reference<css::io::XObjectInputStream>& _rxInStream, sal_Int16& _rValue) +{ + _rValue = _rxInStream->readShort(); + return _rxInStream; +} + + +const css::uno::Reference<css::io::XObjectOutputStream>& operator << (const css::uno::Reference<css::io::XObjectOutputStream>& _rxOutStream, sal_Int16 _nValue) +{ + _rxOutStream->writeShort(_nValue); + return _rxOutStream; +} + + +const css::uno::Reference<css::io::XObjectInputStream>& operator >> (const css::uno::Reference<css::io::XObjectInputStream>& _rxInStream, sal_uInt16& _rValue) +{ + _rValue = _rxInStream->readShort(); + return _rxInStream; +} + + +const css::uno::Reference<css::io::XObjectOutputStream>& operator << (const css::uno::Reference<css::io::XObjectOutputStream>& _rxOutStream, sal_uInt16 _nValue) +{ + _rxOutStream->writeShort(_nValue); + return _rxOutStream; +} + + +const css::uno::Reference<css::io::XObjectInputStream>& operator >> (const css::uno::Reference<css::io::XObjectInputStream>& _rxInStream, sal_uInt32& _rValue) +{ + _rValue = _rxInStream->readLong(); + return _rxInStream; +} + + +const css::uno::Reference<css::io::XObjectOutputStream>& operator << (const css::uno::Reference<css::io::XObjectOutputStream>& _rxOutStream, sal_uInt32 _nValue) +{ + _rxOutStream->writeLong(_nValue); + return _rxOutStream; +} + + +const css::uno::Reference<css::io::XObjectInputStream>& operator >> (const css::uno::Reference<css::io::XObjectInputStream>& _rxInStream, sal_Int32& _rValue) +{ + _rValue = _rxInStream->readLong(); + return _rxInStream; +} + + +const css::uno::Reference<css::io::XObjectOutputStream>& operator << (const css::uno::Reference<css::io::XObjectOutputStream>& _rxOutStream, sal_Int32 _nValue) +{ + _rxOutStream->writeLong(_nValue); + return _rxOutStream; +} + +ByteReader::~ByteReader() {} + +const css::uno::Sequence< sal_Int8 > & ByteReader::getUnoTunnelId() +{ + static const comphelper::UnoIdInit implId; + return implId.getSeq(); +} + +ByteWriter::~ByteWriter() {} + +const css::uno::Sequence< sal_Int8 > & ByteWriter::getUnoTunnelId() +{ + static const comphelper::UnoIdInit implId; + return implId.getSeq(); +} + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/streaming/memorystream.cxx b/comphelper/source/streaming/memorystream.cxx new file mode 100644 index 000000000..8bf3c3e90 --- /dev/null +++ b/comphelper/source/streaming/memorystream.cxx @@ -0,0 +1,286 @@ +/* -*- 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 <algorithm> +#include <memory> + +#include <boost/core/noinit_adaptor.hpp> + +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/XSeekableInputStream.hpp> +#include <com/sun/star/io/XTruncate.hpp> +//#include <com/sun/star/uno/XComponentContext.hpp> +#include <comphelper/bytereader.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include <string.h> +#include <vector> + +namespace com::sun::star::uno { class XComponentContext; } + +using ::cppu::OWeakObject; +using ::cppu::WeakImplHelper; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::osl; + +namespace comphelper +{ + +namespace { + +class UNOMemoryStream : + public WeakImplHelper<XServiceInfo, XStream, XSeekableInputStream, XOutputStream, XTruncate, XUnoTunnel>, + public comphelper::ByteWriter +{ +public: + UNOMemoryStream(); + + // 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; + + // XStream + virtual Reference< XInputStream > SAL_CALL getInputStream( ) override; + virtual Reference< XOutputStream > SAL_CALL getOutputStream( ) override; + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) override; + virtual sal_Int32 SAL_CALL readSomeBytes( Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + virtual sal_Int32 SAL_CALL available() override; + virtual void SAL_CALL closeInput() override; + + // XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + virtual sal_Int64 SAL_CALL getPosition() override; + virtual sal_Int64 SAL_CALL getLength() override; + + // XOutputStream + virtual void SAL_CALL writeBytes( const Sequence< sal_Int8 >& aData ) override; + virtual void SAL_CALL flush() override; + virtual void SAL_CALL closeOutput() override; + + // XTruncate + virtual void SAL_CALL truncate() override; + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& aIdentifier ) override; + + // comphelper::ByteWriter + virtual sal_Int32 writeSomeBytes(const sal_Int8* aData, sal_Int32 nBytesToWrite) override; + +private: + std::vector< sal_Int8, boost::noinit_adaptor<std::allocator<sal_Int8>> > maData; + sal_Int32 mnCursor; +}; + +} + +UNOMemoryStream::UNOMemoryStream() +: mnCursor(0) +{ + maData.reserve(1 * 1024 * 1024); +} + +// XServiceInfo +OUString SAL_CALL UNOMemoryStream::getImplementationName() +{ + return "com.sun.star.comp.MemoryStream"; +} + +sal_Bool SAL_CALL UNOMemoryStream::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence<OUString> SAL_CALL UNOMemoryStream::getSupportedServiceNames() +{ + return { "com.sun.star.comp.MemoryStream" }; +} + +// XStream +Reference< XInputStream > SAL_CALL UNOMemoryStream::getInputStream( ) +{ + return this; +} + +Reference< XOutputStream > SAL_CALL UNOMemoryStream::getOutputStream( ) +{ + return this; +} + +// XInputStream +sal_Int32 SAL_CALL UNOMemoryStream::readBytes( Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + if( nBytesToRead < 0 ) + throw IOException("nBytesToRead < 0"); + + nBytesToRead = std::min( nBytesToRead, available() ); + aData.realloc( nBytesToRead ); + + if( nBytesToRead ) + { + sal_Int8* pData = &(*maData.begin()); + sal_Int8* pCursor = &(pData[mnCursor]); + memcpy( aData.getArray(), pCursor, nBytesToRead ); + + mnCursor += nBytesToRead; + } + + return nBytesToRead; +} + +sal_Int32 SAL_CALL UNOMemoryStream::readSomeBytes( Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + return readBytes( aData, nMaxBytesToRead ); +} + +void SAL_CALL UNOMemoryStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + if( nBytesToSkip < 0 ) + throw IOException("nBytesToSkip < 0"); + + mnCursor += std::min( nBytesToSkip, available() ); +} + +sal_Int32 SAL_CALL UNOMemoryStream::available() +{ + return std::min<sal_Int64>( SAL_MAX_INT32, maData.size() - mnCursor); +} + +void SAL_CALL UNOMemoryStream::closeInput() +{ + mnCursor = 0; +} + +// XSeekable +void SAL_CALL UNOMemoryStream::seek( sal_Int64 location ) +{ + if( (location < 0) || (location > SAL_MAX_INT32) ) + throw IllegalArgumentException("this implementation does not support more than 2GB!", static_cast<OWeakObject*>(this), 0 ); + + // seek operation should be able to resize the stream + if ( o3tl::make_unsigned(location) > maData.size() ) + maData.resize( static_cast< sal_Int32 >( location ) ); + + mnCursor = static_cast< sal_Int32 >( location ); +} + +sal_Int64 SAL_CALL UNOMemoryStream::getPosition() +{ + return static_cast< sal_Int64 >( mnCursor ); +} + +sal_Int64 SAL_CALL UNOMemoryStream::getLength() +{ + return static_cast< sal_Int64 >( maData.size() ); +} + +// XOutputStream +void SAL_CALL UNOMemoryStream::writeBytes( const Sequence< sal_Int8 >& aData ) +{ + const sal_Int32 nBytesToWrite( aData.getLength() ); + if( !nBytesToWrite ) + return; + + sal_Int64 nNewSize = static_cast<sal_Int64>(mnCursor) + nBytesToWrite; + if( nNewSize > SAL_MAX_INT32 ) + { + OSL_ASSERT(false); + throw IOException("this implementation does not support more than 2GB!", static_cast<OWeakObject*>(this) ); + } + + if( static_cast< sal_Int32 >( nNewSize ) > static_cast< sal_Int32 >( maData.size() ) ) + maData.resize( nNewSize ); + + sal_Int8* pData = &(*maData.begin()); + sal_Int8* pCursor = &(pData[mnCursor]); + memcpy(pCursor, aData.getConstArray(), nBytesToWrite); + + mnCursor += nBytesToWrite; +} + +sal_Int32 UNOMemoryStream::writeSomeBytes( const sal_Int8* pInData, sal_Int32 nBytesToWrite ) +{ + if( !nBytesToWrite ) + return 0; + + sal_Int64 nNewSize = static_cast<sal_Int64>(mnCursor) + nBytesToWrite; + if( nNewSize > SAL_MAX_INT32 ) + { + OSL_ASSERT(false); + throw IOException("this implementation does not support more than 2GB!", static_cast<OWeakObject*>(this) ); + } + + if( static_cast< sal_Int32 >( nNewSize ) > static_cast< sal_Int32 >( maData.size() ) ) + maData.resize( nNewSize ); + + sal_Int8* pData = &(*maData.begin()); + sal_Int8* pCursor = &(pData[mnCursor]); + // cast to avoid -Werror=class-memaccess + memcpy(pCursor, pInData, nBytesToWrite); + + mnCursor += nBytesToWrite; + return nBytesToWrite; +} + +void SAL_CALL UNOMemoryStream::flush() +{ +} + +void SAL_CALL UNOMemoryStream::closeOutput() +{ + mnCursor = 0; +} + +//XTruncate +void SAL_CALL UNOMemoryStream::truncate() +{ + maData.clear(); + mnCursor = 0; +} + +sal_Int64 SAL_CALL UNOMemoryStream::getSomething( const css::uno::Sequence< sal_Int8 >& rIdentifier ) +{ + if (rIdentifier == comphelper::ByteWriter::getUnoTunnelId()) + return reinterpret_cast<sal_Int64>(static_cast<comphelper::ByteWriter*>(this)); + return 0; +} + +} // namespace comphelper + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_MemoryStream( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new ::comphelper::UNOMemoryStream()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/streaming/oslfile2streamwrap.cxx b/comphelper/source/streaming/oslfile2streamwrap.cxx new file mode 100644 index 000000000..243634610 --- /dev/null +++ b/comphelper/source/streaming/oslfile2streamwrap.cxx @@ -0,0 +1,171 @@ +/* -*- 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 <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <comphelper/oslfile2streamwrap.hxx> +#include <o3tl/safeint.hxx> +#include <osl/file.hxx> + +#include <algorithm> + +namespace comphelper +{ + using namespace osl; + + +OSLInputStreamWrapper::OSLInputStreamWrapper( File& _rFile ) + : m_pFile(&_rFile) +{ +} + + +OSLInputStreamWrapper::~OSLInputStreamWrapper() +{ +} + + +sal_Int32 SAL_CALL OSLInputStreamWrapper::readBytes(css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead) +{ + if (!m_pFile) + throw css::io::NotConnectedException(OUString(), static_cast<css::uno::XWeak*>(this)); + + if (nBytesToRead < 0) + throw css::io::BufferSizeExceededException(OUString(),static_cast<css::uno::XWeak*>(this)); + + aData.realloc(nBytesToRead); + + std::scoped_lock aGuard( m_aMutex ); + + sal_uInt64 nRead = 0; + FileBase::RC eError = m_pFile->read(static_cast<void*>(aData.getArray()), nBytesToRead, nRead); + if (eError != FileBase::E_None) + throw css::io::BufferSizeExceededException(OUString(),static_cast<css::uno::XWeak*>(this)); + + // If the read character < MaxLength, adjust css::uno::Sequence + if (nRead < o3tl::make_unsigned(nBytesToRead)) + aData.realloc( sal::static_int_cast< sal_Int32 >(nRead) ); + + return sal::static_int_cast< sal_Int32 >(nRead); +} + +sal_Int32 SAL_CALL OSLInputStreamWrapper::readSomeBytes(css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead) +{ + if (!m_pFile) + throw css::io::NotConnectedException(OUString(), static_cast<css::uno::XWeak*>(this)); + + if (nMaxBytesToRead < 0) + throw css::io::BufferSizeExceededException(OUString(),static_cast<css::uno::XWeak*>(this)); + + return readBytes(aData, nMaxBytesToRead); +} + +void SAL_CALL OSLInputStreamWrapper::skipBytes(sal_Int32 nBytesToSkip) +{ + std::scoped_lock aGuard( m_aMutex ); + if (!m_pFile) + throw css::io::NotConnectedException(OUString(), static_cast<css::uno::XWeak*>(this)); + + sal_uInt64 nCurrentPos; + FileBase::RC eError = m_pFile->getPos(nCurrentPos); + if (eError != FileBase::E_None) + throw css::io::NotConnectedException(OUString(), static_cast<css::uno::XWeak*>(this)); + + sal_uInt64 nNewPos = nCurrentPos + nBytesToSkip; + eError = m_pFile->setPos(osl_Pos_Absolut, nNewPos); + if (eError != FileBase::E_None) + throw css::io::NotConnectedException(OUString(), static_cast<css::uno::XWeak*>(this)); +} + +sal_Int32 SAL_CALL OSLInputStreamWrapper::available() +{ + std::scoped_lock aGuard( m_aMutex ); + if (!m_pFile) + throw css::io::NotConnectedException(OUString(), static_cast<css::uno::XWeak*>(this)); + + sal_uInt64 nPos; + FileBase::RC eError = m_pFile->getPos(nPos); + if (eError != FileBase::E_None) + throw css::io::NotConnectedException(OUString(), static_cast<css::uno::XWeak*>(this)); + + eError = m_pFile->setPos(osl_Pos_End, 0); + if (eError != FileBase::E_None) + throw css::io::NotConnectedException(OUString(),static_cast<css::uno::XWeak*>(this)); + + sal_uInt64 nAvailable; + eError = m_pFile->getPos(nAvailable); + if (eError != FileBase::E_None) + throw css::io::NotConnectedException(OUString(),static_cast<css::uno::XWeak*>(this)); + + nAvailable = nAvailable - nPos; + eError = m_pFile->setPos(osl_Pos_Absolut, nPos); + if (eError != FileBase::E_None) + throw css::io::NotConnectedException(OUString(),static_cast<css::uno::XWeak*>(this)); + return std::min<sal_Int64>(nAvailable, SAL_MAX_INT32); +} + + +void SAL_CALL OSLInputStreamWrapper::closeInput() +{ + if (!m_pFile) + throw css::io::NotConnectedException(OUString(), static_cast<css::uno::XWeak*>(this)); + + m_pFile->close(); + + m_pFile = nullptr; +} + +/*************************************************************************/ +// css::io::XOutputStream + + +OSLOutputStreamWrapper::OSLOutputStreamWrapper(osl::File & _rFile): + rFile(_rFile) +{} + +OSLOutputStreamWrapper::~OSLOutputStreamWrapper() {} + +void SAL_CALL OSLOutputStreamWrapper::writeBytes(const css::uno::Sequence< sal_Int8 >& aData) +{ + sal_uInt64 nWritten; + FileBase::RC eError = rFile.write(aData.getConstArray(),aData.getLength(), nWritten); + if (eError != FileBase::E_None + || nWritten != sal::static_int_cast< sal_uInt32 >(aData.getLength())) + { + throw css::io::BufferSizeExceededException(OUString(),static_cast<css::uno::XWeak*>(this)); + } +} + + +void SAL_CALL OSLOutputStreamWrapper::flush() +{ +} + + +void SAL_CALL OSLOutputStreamWrapper::closeOutput() +{ + rFile.close(); +} + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/streaming/seekableinput.cxx b/comphelper/source/streaming/seekableinput.cxx new file mode 100644 index 000000000..3508f933e --- /dev/null +++ b/comphelper/source/streaming/seekableinput.cxx @@ -0,0 +1,233 @@ +/* -*- 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 <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/io/XOutputStream.hpp> + + +#include <comphelper/seekableinput.hxx> +#include <utility> + +using namespace ::com::sun::star; + +namespace comphelper +{ + +const sal_Int32 nConstBufferSize = 32000; + + +static void copyInputToOutput_Impl( const uno::Reference< io::XInputStream >& xIn, + const uno::Reference< io::XOutputStream >& xOut ) +{ + sal_Int32 nRead; + uno::Sequence< sal_Int8 > aSequence( nConstBufferSize ); + + do + { + nRead = xIn->readBytes( aSequence, nConstBufferSize ); + if ( nRead < nConstBufferSize ) + { + uno::Sequence< sal_Int8 > aTempBuf( aSequence.getConstArray(), nRead ); + xOut->writeBytes( aTempBuf ); + } + else + xOut->writeBytes( aSequence ); + } + while ( nRead == nConstBufferSize ); +} + + +OSeekableInputWrapper::OSeekableInputWrapper( + uno::Reference< io::XInputStream > xInStream, + uno::Reference< uno::XComponentContext > xContext ) +: m_xContext(std::move( xContext )) +, m_xOriginalStream(std::move( xInStream )) +{ + if ( !m_xContext.is() ) + throw uno::RuntimeException(); +} + + +OSeekableInputWrapper::~OSeekableInputWrapper() +{ +} + + +uno::Reference< io::XInputStream > OSeekableInputWrapper::CheckSeekableCanWrap( + const uno::Reference< io::XInputStream >& xInStream, + const uno::Reference< uno::XComponentContext >& rxContext ) +{ + // check that the stream is seekable and just wrap it if it is not + uno::Reference< io::XSeekable > xSeek( xInStream, uno::UNO_QUERY ); + if ( xSeek.is() ) + return xInStream; + + return new OSeekableInputWrapper(xInStream, rxContext); +} + + +void OSeekableInputWrapper::PrepareCopy_Impl() +{ + if ( !m_xCopyInput.is() ) + { + if ( !m_xContext.is() ) + throw uno::RuntimeException(); + + uno::Reference< io::XOutputStream > xTempOut( + io::TempFile::create(m_xContext), + uno::UNO_QUERY_THROW ); + + copyInputToOutput_Impl( m_xOriginalStream, xTempOut ); + xTempOut->closeOutput(); + + uno::Reference< io::XSeekable > xTempSeek( xTempOut, uno::UNO_QUERY ); + if ( xTempSeek.is() ) + { + xTempSeek->seek( 0 ); + m_xCopyInput.set( xTempOut, uno::UNO_QUERY ); + if ( m_xCopyInput.is() ) + m_xCopySeek = xTempSeek; + } + } + + if ( !m_xCopyInput.is() ) + throw io::IOException("no m_xCopyInput"); +} + +// XInputStream + +sal_Int32 SAL_CALL OSeekableInputWrapper::readBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_xOriginalStream.is() ) + throw io::NotConnectedException(); + + PrepareCopy_Impl(); + + return m_xCopyInput->readBytes( aData, nBytesToRead ); +} + + +sal_Int32 SAL_CALL OSeekableInputWrapper::readSomeBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_xOriginalStream.is() ) + throw io::NotConnectedException(); + + PrepareCopy_Impl(); + + return m_xCopyInput->readSomeBytes( aData, nMaxBytesToRead ); +} + + +void SAL_CALL OSeekableInputWrapper::skipBytes( sal_Int32 nBytesToSkip ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_xOriginalStream.is() ) + throw io::NotConnectedException(); + + PrepareCopy_Impl(); + + m_xCopyInput->skipBytes( nBytesToSkip ); +} + + +sal_Int32 SAL_CALL OSeekableInputWrapper::available() +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_xOriginalStream.is() ) + throw io::NotConnectedException(); + + PrepareCopy_Impl(); + + return m_xCopyInput->available(); +} + + +void SAL_CALL OSeekableInputWrapper::closeInput() +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_xOriginalStream.is() ) + throw io::NotConnectedException(); + + m_xOriginalStream->closeInput(); + m_xOriginalStream.clear(); + + if ( m_xCopyInput.is() ) + { + m_xCopyInput->closeInput(); + m_xCopyInput.clear(); + } + + m_xCopySeek.clear(); +} + + +// XSeekable + +void SAL_CALL OSeekableInputWrapper::seek( sal_Int64 location ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_xOriginalStream.is() ) + throw io::NotConnectedException(); + + PrepareCopy_Impl(); + + m_xCopySeek->seek( location ); +} + + +sal_Int64 SAL_CALL OSeekableInputWrapper::getPosition() +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_xOriginalStream.is() ) + throw io::NotConnectedException(); + + PrepareCopy_Impl(); + + return m_xCopySeek->getPosition(); +} + + +sal_Int64 SAL_CALL OSeekableInputWrapper::getLength() +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_xOriginalStream.is() ) + throw io::NotConnectedException(); + + PrepareCopy_Impl(); + + return m_xCopySeek->getLength(); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/streaming/seqinputstreamserv.cxx b/comphelper/source/streaming/seqinputstreamserv.cxx new file mode 100644 index 000000000..5d10029a5 --- /dev/null +++ b/comphelper/source/streaming/seqinputstreamserv.cxx @@ -0,0 +1,215 @@ +/* -*- 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 <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/seqstream.hxx> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/XSeekableInputStream.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/frame/DoubleInitializationException.hpp> +#include <mutex> + +namespace com::sun::star::uno { class XComponentContext; } + +using namespace ::com::sun::star; + +namespace { + +class SequenceInputStreamService: + public ::cppu::WeakImplHelper< + lang::XServiceInfo, + io::XSeekableInputStream, + lang::XInitialization> +{ +public: + explicit SequenceInputStreamService(); + + // noncopyable + SequenceInputStreamService(const SequenceInputStreamService&) = delete; + const SequenceInputStreamService& operator=(const SequenceInputStreamService&) = delete; + + // css::lang::XServiceInfo: + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString & ServiceName ) override; + virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // css::io::XInputStream: + virtual ::sal_Int32 SAL_CALL readBytes( uno::Sequence< ::sal_Int8 > & aData, ::sal_Int32 nBytesToRead ) override; + virtual ::sal_Int32 SAL_CALL readSomeBytes( uno::Sequence< ::sal_Int8 > & aData, ::sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( ::sal_Int32 nBytesToSkip ) override; + virtual ::sal_Int32 SAL_CALL available() override; + virtual void SAL_CALL closeInput() override; + + // css::io::XSeekable: + virtual void SAL_CALL seek( ::sal_Int64 location ) override; + virtual ::sal_Int64 SAL_CALL getPosition() override; + virtual ::sal_Int64 SAL_CALL getLength() override; + + // css::lang::XInitialization: + virtual void SAL_CALL initialize( const uno::Sequence< css::uno::Any > & aArguments ) override; + +private: + virtual ~SequenceInputStreamService() override {} + + + std::mutex m_aMutex; + bool m_bInitialized; + uno::Reference< io::XInputStream > m_xInputStream; + uno::Reference< io::XSeekable > m_xSeekable; +}; + +SequenceInputStreamService::SequenceInputStreamService() +: m_bInitialized( false ) +{} + +// com.sun.star.uno.XServiceInfo: +OUString SAL_CALL SequenceInputStreamService::getImplementationName() +{ + return "com.sun.star.comp.SequenceInputStreamService"; +} + +sal_Bool SAL_CALL SequenceInputStreamService::supportsService( OUString const & serviceName ) +{ + return cppu::supportsService(this, serviceName); +} + +uno::Sequence< OUString > SAL_CALL SequenceInputStreamService::getSupportedServiceNames() +{ + return { "com.sun.star.io.SequenceInputStream" }; +} + +// css::io::XInputStream: +::sal_Int32 SAL_CALL SequenceInputStreamService::readBytes( uno::Sequence< ::sal_Int8 > & aData, ::sal_Int32 nBytesToRead ) +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_xInputStream.is() ) + throw io::NotConnectedException(); + + return m_xInputStream->readBytes( aData, nBytesToRead ); +} + +::sal_Int32 SAL_CALL SequenceInputStreamService::readSomeBytes( uno::Sequence< ::sal_Int8 > & aData, ::sal_Int32 nMaxBytesToRead ) +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_xInputStream.is() ) + throw io::NotConnectedException(); + + return m_xInputStream->readSomeBytes( aData, nMaxBytesToRead ); +} + +void SAL_CALL SequenceInputStreamService::skipBytes( ::sal_Int32 nBytesToSkip ) +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_xInputStream.is() ) + throw io::NotConnectedException(); + + return m_xInputStream->skipBytes( nBytesToSkip ); +} + +::sal_Int32 SAL_CALL SequenceInputStreamService::available() +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_xInputStream.is() ) + throw io::NotConnectedException(); + + return m_xInputStream->available(); +} + +void SAL_CALL SequenceInputStreamService::closeInput() +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_xInputStream.is() ) + throw io::NotConnectedException(); + + m_xInputStream->closeInput(); + m_xInputStream.clear(); + m_xSeekable.clear(); +} + +// css::io::XSeekable: +void SAL_CALL SequenceInputStreamService::seek( ::sal_Int64 location ) +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_xSeekable.is() ) + throw io::NotConnectedException(); + + m_xSeekable->seek( location ); +} + +::sal_Int64 SAL_CALL SequenceInputStreamService::getPosition() +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_xSeekable.is() ) + throw io::NotConnectedException(); + + return m_xSeekable->getPosition(); +} + +::sal_Int64 SAL_CALL SequenceInputStreamService::getLength() +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_xSeekable.is() ) + throw io::NotConnectedException(); + + return m_xSeekable->getLength(); +} + +// css::lang::XInitialization: +void SAL_CALL SequenceInputStreamService::initialize( const uno::Sequence< css::uno::Any > & aArguments ) +{ + std::scoped_lock aGuard( m_aMutex ); + if ( m_bInitialized ) + throw frame::DoubleInitializationException(); + + if ( aArguments.getLength() != 1 ) + throw lang::IllegalArgumentException( "Wrong number of arguments!", + static_cast< ::cppu::OWeakObject* >(this), + 1 ); + + uno::Sequence< sal_Int8 > aSeq; + if ( !(aArguments[0] >>= aSeq) ) + throw lang::IllegalArgumentException( "Unexpected type of argument!", + static_cast< ::cppu::OWeakObject* >(this), + 1 ); + + uno::Reference< io::XInputStream > xInputStream( + static_cast< ::cppu::OWeakObject* >( new ::comphelper::SequenceInputStream( aSeq ) ), + uno::UNO_QUERY_THROW ); + uno::Reference< io::XSeekable > xSeekable( xInputStream, uno::UNO_QUERY_THROW ); + m_xInputStream = xInputStream; + m_xSeekable = xSeekable; + m_bInitialized = true; +} + +} // anonymous namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_SequenceInputStreamService( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SequenceInputStreamService()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/streaming/seqoutputstreamserv.cxx b/comphelper/source/streaming/seqoutputstreamserv.cxx new file mode 100644 index 000000000..19ef79002 --- /dev/null +++ b/comphelper/source/streaming/seqoutputstreamserv.cxx @@ -0,0 +1,144 @@ +/* -*- 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 <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/seqstream.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/XSequenceOutputStream.hpp> +#include <mutex> + +namespace com::sun::star::uno { class XComponentContext; } + +using namespace ::com::sun::star; + + +namespace { + +class SequenceOutputStreamService: + public cppu::WeakImplHelper<lang::XServiceInfo, io::XSequenceOutputStream> +{ +public: + explicit SequenceOutputStreamService(); + + // noncopyable + SequenceOutputStreamService(const SequenceOutputStreamService&) = delete; + const SequenceOutputStreamService& operator=(const SequenceOutputStreamService&) = delete; + + // css::lang::XServiceInfo: + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString & ServiceName ) override; + virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // css::io::XOutputStream: + virtual void SAL_CALL writeBytes( const uno::Sequence< ::sal_Int8 > & aData ) override; + virtual void SAL_CALL flush() override; + virtual void SAL_CALL closeOutput() override; + + // css::io::XSequenceOutputStream: + virtual uno::Sequence< ::sal_Int8 > SAL_CALL getWrittenBytes( ) override; + +private: + virtual ~SequenceOutputStreamService() override {}; + + + std::mutex m_aMutex; + // WARNING: dtor of m_xOutputStream writes into m_aSequence so that must live longer! + uno::Sequence< ::sal_Int8 > m_aSequence; + uno::Reference< io::XOutputStream > m_xOutputStream; +}; +SequenceOutputStreamService::SequenceOutputStreamService() +{ + m_xOutputStream.set( static_cast < ::cppu::OWeakObject* >( new ::comphelper::OSequenceOutputStream( m_aSequence ) ), uno::UNO_QUERY_THROW ); +} + +// com.sun.star.uno.XServiceInfo: +OUString SAL_CALL SequenceOutputStreamService::getImplementationName() +{ + return "com.sun.star.comp.SequenceOutputStreamService"; +} + +sal_Bool SAL_CALL SequenceOutputStreamService::supportsService( OUString const & serviceName ) +{ + return cppu::supportsService(this, serviceName); +} + +uno::Sequence< OUString > SAL_CALL SequenceOutputStreamService::getSupportedServiceNames() +{ + return { "com.sun.star.io.SequenceOutputStream" }; +} + +// css::io::XOutputStream: +void SAL_CALL SequenceOutputStreamService::writeBytes( const uno::Sequence< ::sal_Int8 > & aData ) +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_xOutputStream.is() ) + throw io::NotConnectedException(); + + m_xOutputStream->writeBytes( aData ); +} + +void SAL_CALL SequenceOutputStreamService::flush() +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_xOutputStream.is() ) + throw io::NotConnectedException(); + + m_xOutputStream->flush(); +}; + +void SAL_CALL SequenceOutputStreamService::closeOutput() +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_xOutputStream.is() ) + throw io::NotConnectedException(); + + m_xOutputStream->flush(); + m_xOutputStream->closeOutput(); + m_xOutputStream.clear(); +} + +// css::io::XSequenceOutputStream: +uno::Sequence< ::sal_Int8 > SAL_CALL SequenceOutputStreamService::getWrittenBytes() +{ + std::scoped_lock aGuard( m_aMutex ); + + if (m_xOutputStream.is()) + { + m_xOutputStream->flush(); + } + // else: no exception, just return the finished sequence + + return m_aSequence; +} + +} // anonymous namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_SequenceOutputStreamService( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SequenceOutputStreamService()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/streaming/seqstream.cxx b/comphelper/source/streaming/seqstream.cxx new file mode 100644 index 000000000..1f37a7967 --- /dev/null +++ b/comphelper/source/streaming/seqstream.cxx @@ -0,0 +1,247 @@ +/* -*- 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 <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <comphelper/seqstream.hxx> + +#include <osl/diagnose.h> + +namespace comphelper +{ +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::uno; +using namespace ::osl; + + + + +SequenceInputStream::SequenceInputStream( + css::uno::Sequence<sal_Int8> const & rData) +: m_aData(rData) +, m_nPos(0) +{ +} + +// checks if closed, returns available size, not mutex-protected + +inline sal_Int32 SequenceInputStream::avail() +{ + if (m_nPos == -1) + throw NotConnectedException(OUString(), *this); + + return m_aData.getLength() - m_nPos; +} + +// css::io::XInputStream + +sal_Int32 SAL_CALL SequenceInputStream::readBytes( Sequence<sal_Int8>& aData, sal_Int32 nBytesToRead ) +{ + if (nBytesToRead < 0) + throw BufferSizeExceededException(OUString(),*this); + + std::scoped_lock aGuard( m_aMutex ); + + sal_Int32 nAvail = avail(); + + if (nAvail < nBytesToRead) + nBytesToRead = nAvail; + + aData.realloc(nBytesToRead); + memcpy(aData.getArray(), m_aData.getConstArray() + m_nPos, nBytesToRead); + m_nPos += nBytesToRead; + + return nBytesToRead; +} + +sal_Int32 SequenceInputStream::readSomeBytes( sal_Int8* pData, sal_Int32 nBytesToRead ) +{ + if (nBytesToRead < 0) + throw BufferSizeExceededException(OUString(),*this); + + std::scoped_lock aGuard( m_aMutex ); + + sal_Int32 nAvail = avail(); + + if (nAvail < nBytesToRead) + nBytesToRead = nAvail; + + memcpy(pData, m_aData.getConstArray() + m_nPos, nBytesToRead); + m_nPos += nBytesToRead; + + return nBytesToRead; +} + +sal_Int64 SAL_CALL SequenceInputStream::getSomething( const css::uno::Sequence< sal_Int8 >& rIdentifier ) +{ + if (rIdentifier == comphelper::ByteReader::getUnoTunnelId()) + return reinterpret_cast<sal_Int64>(static_cast<comphelper::ByteReader*>(this)); + return 0; +} + +sal_Int32 SAL_CALL SequenceInputStream::readSomeBytes( Sequence<sal_Int8>& aData, sal_Int32 nMaxBytesToRead ) +{ + // all data is available at once + return readBytes(aData, nMaxBytesToRead); +} + + +void SAL_CALL SequenceInputStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + if (nBytesToSkip < 0) + throw BufferSizeExceededException(OUString(),*this); + + std::scoped_lock aGuard( m_aMutex ); + + sal_Int32 nAvail = avail(); + + if (nAvail < nBytesToSkip) + nBytesToSkip = nAvail; + + m_nPos += nBytesToSkip; +} + + +sal_Int32 SAL_CALL SequenceInputStream::available( ) +{ + std::scoped_lock aGuard( m_aMutex ); + + return avail(); +} + + +void SAL_CALL SequenceInputStream::closeInput( ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if (m_nPos == -1) + throw NotConnectedException(OUString(), *this); + + m_nPos = -1; +} + +void SAL_CALL SequenceInputStream::seek( sal_Int64 location ) +{ + if ( location > m_aData.getLength() || location < 0 || location > SAL_MAX_INT32 ) + throw IllegalArgumentException("bad location", static_cast<cppu::OWeakObject*>(this), 1); + std::scoped_lock aGuard( m_aMutex ); + m_nPos = static_cast<sal_Int32>(location); +} + +sal_Int64 SAL_CALL SequenceInputStream::getPosition() +{ + std::scoped_lock aGuard( m_aMutex ); + return m_nPos; +} + +sal_Int64 SAL_CALL SequenceInputStream::getLength( ) +{ + std::scoped_lock aGuard( m_aMutex ); + return m_aData.getLength(); +} + + +OSequenceOutputStream::OSequenceOutputStream(Sequence< sal_Int8 >& _rSeq, double _nResizeFactor, sal_Int32 _nMinimumResize) + :m_rSequence(_rSeq) + ,m_nResizeFactor(_nResizeFactor) + ,m_nMinimumResize(_nMinimumResize) + ,m_nSize(0) // starting at position 0 + ,m_bConnected(true) +{ + OSL_ENSURE(m_nResizeFactor > 1, "OSequenceOutputStream::OSequenceOutputStream : invalid resize factor !"); + + if (m_nResizeFactor <= 1) + m_nResizeFactor = 1.3; +} + + +void SAL_CALL OSequenceOutputStream::writeBytes( const Sequence< sal_Int8 >& _rData ) +{ + std::scoped_lock aGuard(m_aMutex); + if (!m_bConnected) + throw NotConnectedException(); + + // ensure the sequence has enough space left + if (m_nSize + _rData.getLength() > m_rSequence.getLength()) + { + sal_Int32 nCurrentLength = m_rSequence.getLength(); + sal_Int32 nNewLength = static_cast< sal_Int32 >( + nCurrentLength * m_nResizeFactor); + + if (m_nMinimumResize > nNewLength - nCurrentLength) + // we have a minimum so it's not too inefficient for small sequences and small write requests + nNewLength = nCurrentLength + m_nMinimumResize; + + if (nNewLength < m_nSize + _rData.getLength()) + { // it's not enough... the data would not fit + + // let's take the double amount of the length of the data to be written, as the next write + // request could be as large as this one + sal_Int32 nNewGrowth = _rData.getLength() * 2; + nNewLength = nCurrentLength + nNewGrowth; + } + + // round it off to the next multiple of 4... + nNewLength = (nNewLength + 3) / 4 * 4; + + m_rSequence.realloc(nNewLength); + } + + OSL_ENSURE(m_rSequence.getLength() >= m_nSize + _rData.getLength(), + "ooops ... the realloc algorithm seems to be wrong :( !"); + + memcpy(m_rSequence.getArray() + m_nSize, _rData.getConstArray(), _rData.getLength()); + m_nSize += _rData.getLength(); +} + + +void SAL_CALL OSequenceOutputStream::flush( ) +{ + std::scoped_lock aGuard(m_aMutex); + if (!m_bConnected) + throw NotConnectedException(); + + // cut the sequence to the real size + m_rSequence.realloc(m_nSize); +} + +void OSequenceOutputStream::finalizeOutput() +{ + // cut the sequence to the real size + m_rSequence.realloc(m_nSize); + // and don't allow any further accesses + m_bConnected = false; +} + +void SAL_CALL OSequenceOutputStream::closeOutput() +{ + std::scoped_lock aGuard(m_aMutex); + if (!m_bConnected) + throw NotConnectedException(); + + finalizeOutput(); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/streaming/streamsection.cxx b/comphelper/source/streaming/streamsection.cxx new file mode 100644 index 000000000..b03df8ab7 --- /dev/null +++ b/comphelper/source/streaming/streamsection.cxx @@ -0,0 +1,91 @@ +/* -*- 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/streamsection.hxx> +#include <com/sun/star/io/XMarkableStream.hpp> +#include <com/sun/star/io/XDataInputStream.hpp> +#include <com/sun/star/io/XDataOutputStream.hpp> +#include <osl/diagnose.h> + +namespace comphelper +{ + + +OStreamSection::OStreamSection(const css::uno::Reference< css::io::XDataInputStream >& _rxInput) + :m_xMarkStream(_rxInput, css::uno::UNO_QUERY) + ,m_xInStream(_rxInput) + ,m_nBlockStart(-1) + ,m_nBlockLen(-1) +{ + OSL_ENSURE(m_xInStream.is() && m_xMarkStream.is(), "OStreamSection::OStreamSection : invalid argument !"); + if (m_xInStream.is() && m_xMarkStream.is()) + { + m_nBlockLen = _rxInput->readLong(); + m_nBlockStart = m_xMarkStream->createMark(); + } +} + + +OStreamSection::OStreamSection(const css::uno::Reference< css::io::XDataOutputStream >& _rxOutput) + :m_xMarkStream(_rxOutput, css::uno::UNO_QUERY) + ,m_xOutStream(_rxOutput) + ,m_nBlockStart(-1) + ,m_nBlockLen(-1) +{ + OSL_ENSURE(m_xOutStream.is() && m_xMarkStream.is(), "OStreamSection::OStreamSection : invalid argument !"); + if (m_xOutStream.is() && m_xMarkStream.is()) + { + m_nBlockStart = m_xMarkStream->createMark(); + m_nBlockLen = 0; + m_xOutStream->writeLong(m_nBlockLen); + } +} + + +OStreamSection::~OStreamSection() +{ + try + { // don't allow any exceptions to leave this block, this may be called during the stack unwinding of an exception + // handling routing + if (m_xInStream.is() && m_xMarkStream.is()) + { // we're working on an input stream + m_xMarkStream->jumpToMark(m_nBlockStart); + m_xInStream->skipBytes(m_nBlockLen); + m_xMarkStream->deleteMark(m_nBlockStart); + } + else if (m_xOutStream.is() && m_xMarkStream.is()) + { + sal_Int32 nRealBlockLength = m_xMarkStream->offsetToMark(m_nBlockStart) - sizeof(m_nBlockLen); + m_nBlockLen = nRealBlockLength; + m_xMarkStream->jumpToMark(m_nBlockStart); + m_xOutStream->writeLong(m_nBlockLen); + m_xMarkStream->jumpToFurthest(); + m_xMarkStream->deleteMark(m_nBlockStart); + } + } + catch(const css::uno::Exception&) + { + } +} + + +} // namespace comphelper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/windows/windows_process.cxx b/comphelper/source/windows/windows_process.cxx new file mode 100644 index 000000000..7588bae02 --- /dev/null +++ b/comphelper/source/windows/windows_process.cxx @@ -0,0 +1,262 @@ +/* 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/. */ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <cstring> +#include <wchar.h> + +#include <comphelper/windowsStart.hxx> + +// Needed for CreateEnvironmentBlock +#include <userenv.h> +#pragma comment(lib, "userenv.lib") + +/** + * Get the length that the string will take and takes into account the + * additional length if the string needs to be quoted and if characters need to + * be escaped. + */ +static int ArgStrLen(const wchar_t *s) +{ + int i = wcslen(s); + bool hasDoubleQuote = wcschr(s, L'"') != nullptr; + // Only add doublequotes if the string contains a space or a tab + bool addDoubleQuotes = wcspbrk(s, L" \t") != nullptr; + + if (addDoubleQuotes) + { + i += 2; // initial and final doublequote + } + + if (hasDoubleQuote) + { + int backslashes = 0; + while (*s) + { + if (*s == '\\') + { + ++backslashes; + } + else + { + if (*s == '"') + { + // Escape the doublequote and all backslashes preceding the doublequote + i += backslashes + 1; + } + + backslashes = 0; + } + + ++s; + } + } + + return i; +} + +/** + * Copy string "s" to string "d", quoting the argument as appropriate and + * escaping doublequotes along with any backslashes that immediately precede + * doublequotes. + * The CRT parses this to retrieve the original argc/argv that we meant, + * see STDARGV.C in the MSVC CRT sources. + * + * @return the end of the string + */ +static wchar_t* ArgToString(wchar_t *d, const wchar_t *s) +{ + bool hasDoubleQuote = wcschr(s, L'"') != nullptr; + // Only add doublequotes if the string contains a space or a tab + bool addDoubleQuotes = wcspbrk(s, L" \t") != nullptr; + + if (addDoubleQuotes) + { + *d = '"'; // initial doublequote + ++d; + } + + if (hasDoubleQuote) + { + int backslashes = 0; + while (*s) + { + if (*s == '\\') + { + ++backslashes; + } + else + { + if (*s == '"') + { + // Escape the doublequote and all backslashes preceding the doublequote + for (int i = 0; i <= backslashes; ++i) + { + *d = '\\'; + ++d; + } + } + + backslashes = 0; + } + + *d = *s; + ++d; + ++s; + } + } + else + { + wcscpy(d, s); + d += wcslen(s); + } + + if (addDoubleQuotes) + { + *d = '"'; // final doublequote + ++d; + } + + return d; +} + +/** + * Creates a command line from a list of arguments. The returned + * string is allocated with "malloc" and should be "free"d. + * + * argv is UTF8 + */ +wchar_t* +MakeCommandLine(int argc, wchar_t **argv) +{ + int i; + int len = 0; + + // The + 1 of the last argument handles the allocation for null termination + for (i = 0; i < argc && argv[i]; ++i) + len += ArgStrLen(argv[i]) + 1; + + // Protect against callers that pass 0 arguments + if (len == 0) + len = 1; + + wchar_t *s = static_cast<wchar_t*>(malloc(len * sizeof(wchar_t))); + if (!s) + return nullptr; + + wchar_t *c = s; + for (i = 0; i < argc && argv[i]; ++i) + { + c = ArgToString(c, argv[i]); + if (i + 1 != argc) + { + *c = ' '; + ++c; + } + } + + *c = '\0'; + + return s; +} + +BOOL +WinLaunchChild(const wchar_t *exePath, + int argc, + wchar_t **argv, + HANDLE userToken, + HANDLE *hProcess) +{ + wchar_t *cl; + bool ok; + + cl = MakeCommandLine(argc, argv); + if (!cl) + { + return FALSE; + } + + STARTUPINFOW si; + std::memset(&si, 0, sizeof si); + si.cb = sizeof(STARTUPINFOW); + si.lpDesktop = const_cast<LPWSTR>(L"winsta0\\Default"); + PROCESS_INFORMATION pi; + std::memset(&pi, 0, sizeof pi); + + if (userToken == nullptr) + { + ok = CreateProcessW(exePath, + cl, + nullptr, // no special security attributes + nullptr, // no special thread attributes + FALSE, // don't inherit filehandles + 0, // creation flags + nullptr, // inherit my environment + nullptr, // use my current directory + &si, + &pi); + } + else + { + // Create an environment block for the process we're about to start using + // the user's token. + LPVOID environmentBlock = nullptr; + if (!CreateEnvironmentBlock(&environmentBlock, userToken, TRUE)) + { + environmentBlock = nullptr; + } + + ok = CreateProcessAsUserW(userToken, + exePath, + cl, + nullptr, // no special security attributes + nullptr, // no special thread attributes + FALSE, // don't inherit filehandles + 0, // creation flags + environmentBlock, + nullptr, // use my current directory + &si, + &pi); + + if (environmentBlock) + { + DestroyEnvironmentBlock(environmentBlock); + } + } + + if (ok) + { + if (hProcess) + { + *hProcess = pi.hProcess; // the caller now owns the HANDLE + } + else + { + CloseHandle(pi.hProcess); + } + CloseHandle(pi.hThread); + } + else + { + LPVOID lpMsgBuf = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast<LPWSTR>(&lpMsgBuf), + 0, + nullptr); + wprintf(L"Error restarting: %s\n", lpMsgBuf ? lpMsgBuf : L"(null)"); + if (lpMsgBuf) + HeapFree(GetProcessHeap(), 0, lpMsgBuf); + } + + free(cl); + + return ok; +} diff --git a/comphelper/source/xml/attributelist.cxx b/comphelper/source/xml/attributelist.cxx new file mode 100644 index 000000000..a02c82f69 --- /dev/null +++ b/comphelper/source/xml/attributelist.cxx @@ -0,0 +1,73 @@ +/* -*- 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/attributelist.hxx> + +using namespace osl; +using namespace com::sun::star; + + +namespace comphelper { + +OUString SAL_CALL AttributeList::getTypeByName( const OUString& sName ) +{ + for (auto const& attribute : mAttributes) + { + if( attribute.sName == sName ) { + return attribute.sType; + } + } + return OUString(); +} + +OUString SAL_CALL AttributeList::getValueByName(const OUString& sName) +{ + for (auto const& attribute : mAttributes) + { + if( attribute.sName == sName ) { + return attribute.sValue; + } + } + return OUString(); +} + +AttributeList::AttributeList() +{ + // performance improvement during adding + mAttributes.reserve(20); +} + +AttributeList::AttributeList(const AttributeList &r) + : cppu::WeakImplHelper<XAttributeList, XCloneable>(r) +{ + mAttributes = r.mAttributes; +} + +AttributeList::~AttributeList() +{ +} + +css::uno::Reference< css::util::XCloneable > AttributeList::createClone() +{ + return new AttributeList( *this ); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/xml/ofopxmlhelper.cxx b/comphelper/source/xml/ofopxmlhelper.cxx new file mode 100644 index 000000000..52f6c6356 --- /dev/null +++ b/comphelper/source/xml/ofopxmlhelper.cxx @@ -0,0 +1,493 @@ +/* -*- 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/ofopxmlhelper.hxx> +#include <comphelper/attributelist.hxx> + +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> + +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <vector> + +#define RELATIONINFO_FORMAT 0 +#define CONTENTTYPE_FORMAT 1 +#define FORMAT_MAX_ID CONTENTTYPE_FORMAT + +using namespace ::com::sun::star; + +namespace comphelper { + +namespace { + +// this helper class is designed to allow to parse ContentType- and Relationship-related information from OfficeOpenXML format +class OFOPXMLHelper_Impl + : public cppu::WeakImplHelper< css::xml::sax::XDocumentHandler > +{ + sal_uInt16 const m_nFormat; // which format to parse + + css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > m_aResultSeq; + std::vector< OUString > m_aElementsSeq; // stack of elements being parsed + + +public: + css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > const & GetParsingResult() const; + + explicit OFOPXMLHelper_Impl( sal_uInt16 nFormat ); // must not be created directly + + // XDocumentHandler + virtual void SAL_CALL startDocument() override; + virtual void SAL_CALL endDocument() override; + virtual void SAL_CALL startElement( const OUString& aName, const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs ) override; + virtual void SAL_CALL endElement( const OUString& aName ) override; + virtual void SAL_CALL characters( const OUString& aChars ) override; + virtual void SAL_CALL ignorableWhitespace( const OUString& aWhitespaces ) override; + virtual void SAL_CALL processingInstruction( const OUString& aTarget, const OUString& aData ) override; + virtual void SAL_CALL setDocumentLocator( const css::uno::Reference< css::xml::sax::XLocator >& xLocator ) override; +}; + +} + +namespace OFOPXMLHelper { + +/// @throws css::uno::Exception +static uno::Sequence<uno::Sequence< beans::StringPair>> ReadSequence_Impl( + const uno::Reference<io::XInputStream>& xInStream, + const OUString& aStringID, sal_uInt16 nFormat, + const uno::Reference<uno::XComponentContext>& xContext); + +uno::Sequence< uno::Sequence< beans::StringPair > > ReadRelationsInfoSequence( + const uno::Reference< io::XInputStream >& xInStream, + std::u16string_view aStreamName, + const uno::Reference< uno::XComponentContext >& rContext ) +{ + OUString aStringID = OUString::Concat("_rels/") + aStreamName; + return ReadSequence_Impl( xInStream, aStringID, RELATIONINFO_FORMAT, rContext ); +} + + +uno::Sequence< uno::Sequence< beans::StringPair > > ReadContentTypeSequence( + const uno::Reference< io::XInputStream >& xInStream, + const uno::Reference< uno::XComponentContext >& rContext ) +{ + return ReadSequence_Impl( xInStream, "[Content_Types].xml", CONTENTTYPE_FORMAT, rContext ); +} + +OUString GetContentTypeByName( + const css::uno::Sequence<css::uno::Sequence<css::beans::StringPair>>& rContentTypes, + const OUString& rFilename) +{ + if (rContentTypes.getLength() < 2) + { + return OUString(); + } + + const uno::Sequence<beans::StringPair>& rDefaults = rContentTypes[0]; + const uno::Sequence<beans::StringPair>& rOverrides = rContentTypes[1]; + + // Find the extension and use it to get the type. + const sal_Int32 nDotOffset = rFilename.lastIndexOf('.'); + const OUString aExt = (nDotOffset >= 0 ? rFilename.copy(nDotOffset + 1) : rFilename); // Skip the dot. + + const std::vector<OUString> aNames = { aExt, "/" + rFilename }; + for (const OUString& aName : aNames) + { + const auto it1 = std::find_if(rOverrides.begin(), rOverrides.end(), [&aName](const beans::StringPair& rPair) + { return rPair.First == aName; }); + if (it1 != rOverrides.end()) + return it1->Second; + + const auto it2 = std::find_if(rDefaults.begin(), rDefaults.end(), [&aName](const beans::StringPair& rPair) + { return rPair.First == aName; }); + if (it2 != rDefaults.end()) + return it2->Second; + } + + return OUString(); +} + +void WriteRelationsInfoSequence( + const uno::Reference< io::XOutputStream >& xOutStream, + const uno::Sequence< uno::Sequence< beans::StringPair > >& aSequence, + const uno::Reference< uno::XComponentContext >& rContext ) +{ + if ( !xOutStream.is() ) + throw uno::RuntimeException(); + + uno::Reference< css::xml::sax::XWriter > xWriter = css::xml::sax::Writer::create(rContext); + + xWriter->setOutputStream( xOutStream ); + + OUString aRelListElement( "Relationships" ); + OUString aRelElement( "Relationship" ); + OUString aCDATAString( "CDATA" ); + OUString aWhiteSpace( " " ); + + // write the namespace + rtl::Reference<AttributeList> pRootAttrList = new AttributeList; + pRootAttrList->AddAttribute( + "xmlns", + aCDATAString, + "http://schemas.openxmlformats.org/package/2006/relationships" ); + + xWriter->startDocument(); + xWriter->startElement( aRelListElement, pRootAttrList ); + + for ( const auto & i : aSequence ) + { + rtl::Reference<AttributeList> pAttrList = new AttributeList; + for( const beans::StringPair & pair : i ) + { + if ( !(pair.First == "Id" + || pair.First == "Type" + || pair.First == "TargetMode" + || pair.First == "Target") ) + { + // TODO/LATER: should the extensions be allowed? + throw lang::IllegalArgumentException(); + } + pAttrList->AddAttribute( pair.First, aCDATAString, pair.Second ); + } + + xWriter->startElement( aRelElement, pAttrList ); + xWriter->ignorableWhitespace( aWhiteSpace ); + xWriter->endElement( aRelElement ); + } + + xWriter->ignorableWhitespace( aWhiteSpace ); + xWriter->endElement( aRelListElement ); + xWriter->endDocument(); +} + + +void WriteContentSequence( + const uno::Reference< io::XOutputStream >& xOutStream, + const uno::Sequence< beans::StringPair >& aDefaultsSequence, + const uno::Sequence< beans::StringPair >& aOverridesSequence, + const uno::Reference< uno::XComponentContext >& rContext ) +{ + if ( !xOutStream.is() ) + throw uno::RuntimeException(); + + uno::Reference< css::xml::sax::XWriter > xWriter = css::xml::sax::Writer::create(rContext); + + xWriter->setOutputStream( xOutStream ); + + static constexpr OUStringLiteral aTypesElement(u"Types"); + static constexpr OUStringLiteral aDefaultElement(u"Default"); + static constexpr OUStringLiteral aOverrideElement(u"Override"); + static constexpr OUStringLiteral aContentTypeAttr(u"ContentType"); + static constexpr OUStringLiteral aCDATAString(u"CDATA"); + static constexpr OUStringLiteral aWhiteSpace(u" "); + + // write the namespace + rtl::Reference<AttributeList> pRootAttrList = new AttributeList; + pRootAttrList->AddAttribute( + "xmlns", + aCDATAString, + "http://schemas.openxmlformats.org/package/2006/content-types" ); + + xWriter->startDocument(); + xWriter->startElement( aTypesElement, pRootAttrList ); + + for ( const beans::StringPair & pair : aDefaultsSequence ) + { + rtl::Reference<AttributeList> pAttrList = new AttributeList; + pAttrList->AddAttribute( "Extension", aCDATAString, pair.First ); + pAttrList->AddAttribute( aContentTypeAttr, aCDATAString, pair.Second ); + + xWriter->startElement( aDefaultElement, pAttrList ); + xWriter->ignorableWhitespace( aWhiteSpace ); + xWriter->endElement( aDefaultElement ); + } + + for ( const beans::StringPair & pair : aOverridesSequence ) + { + rtl::Reference<AttributeList> pAttrList = new AttributeList; + pAttrList->AddAttribute( "PartName", aCDATAString, pair.First ); + pAttrList->AddAttribute( aContentTypeAttr, aCDATAString, pair.Second ); + + xWriter->startElement( aOverrideElement, pAttrList ); + xWriter->ignorableWhitespace( aWhiteSpace ); + xWriter->endElement( aOverrideElement ); + } + + xWriter->ignorableWhitespace( aWhiteSpace ); + xWriter->endElement( aTypesElement ); + xWriter->endDocument(); + +} + +uno::Sequence< uno::Sequence< beans::StringPair > > ReadSequence_Impl( + const uno::Reference< io::XInputStream >& xInStream, + const OUString& aStringID, sal_uInt16 nFormat, + const uno::Reference< uno::XComponentContext >& rContext ) +{ + if ( !rContext.is() || !xInStream.is() || nFormat > FORMAT_MAX_ID ) + throw uno::RuntimeException(); + + uno::Reference< css::xml::sax::XParser > xParser = css::xml::sax::Parser::create( rContext ); + + rtl::Reference<OFOPXMLHelper_Impl> pHelper = new OFOPXMLHelper_Impl( nFormat ); + css::xml::sax::InputSource aParserInput; + aParserInput.aInputStream = xInStream; + aParserInput.sSystemId = aStringID; + xParser->setDocumentHandler( pHelper ); + xParser->parseStream( aParserInput ); + xParser->setDocumentHandler( uno::Reference < css::xml::sax::XDocumentHandler > () ); + + return pHelper->GetParsingResult(); +} + +} // namespace OFOPXMLHelper + +// Relations info related strings +constexpr OUStringLiteral g_aRelListElement(u"Relationships"); +constexpr OUStringLiteral g_aRelElement( u"Relationship" ); +constexpr OUStringLiteral g_aIDAttr( u"Id" ); +constexpr OUStringLiteral g_aTypeAttr( u"Type" ); +constexpr OUStringLiteral g_aTargetModeAttr( u"TargetMode" ); +constexpr OUStringLiteral g_aTargetAttr( u"Target" ); + +// ContentType related strings +constexpr OUStringLiteral g_aTypesElement( u"Types" ); +constexpr OUStringLiteral g_aDefaultElement( u"Default" ); +constexpr OUStringLiteral g_aOverrideElement( u"Override" ); +constexpr OUStringLiteral g_aExtensionAttr( u"Extension" ); +constexpr OUStringLiteral g_aPartNameAttr( u"PartName" ); +constexpr OUStringLiteral g_aContentTypeAttr( u"ContentType" ); + +OFOPXMLHelper_Impl::OFOPXMLHelper_Impl( sal_uInt16 nFormat ) +: m_nFormat( nFormat ) +{ +} + +uno::Sequence< uno::Sequence< beans::StringPair > > const & OFOPXMLHelper_Impl::GetParsingResult() const +{ + if ( !m_aElementsSeq.empty() ) + throw uno::RuntimeException(); // the parsing has still not finished! + + return m_aResultSeq; +} + + +void SAL_CALL OFOPXMLHelper_Impl::startDocument() +{ +} + + +void SAL_CALL OFOPXMLHelper_Impl::endDocument() +{ +} + + +void SAL_CALL OFOPXMLHelper_Impl::startElement( const OUString& aName, const uno::Reference< css::xml::sax::XAttributeList >& xAttribs ) +{ + if ( m_nFormat == RELATIONINFO_FORMAT ) + { + if ( aName == g_aRelListElement ) + { + sal_Int32 nNewLength = m_aElementsSeq.size() + 1; + + if ( nNewLength != 1 ) + throw css::xml::sax::SAXException(); // TODO: this element must be the first level element + + m_aElementsSeq.push_back( aName ); + + return; // nothing to do + } + else if ( aName == g_aRelElement ) + { + sal_Int32 nNewLength = m_aElementsSeq.size() + 1; + if ( nNewLength != 2 ) + throw css::xml::sax::SAXException(); // TODO: this element must be the second level element + + m_aElementsSeq.push_back( aName ); + + sal_Int32 nNewEntryNum = m_aResultSeq.getLength() + 1; + m_aResultSeq.realloc( nNewEntryNum ); + auto pResultSeq = m_aResultSeq.getArray(); + sal_Int32 nAttrNum = 0; + pResultSeq[nNewEntryNum-1].realloc( 4 ); // the maximal expected number of arguments is 4 + auto pAttrs = pResultSeq[nNewEntryNum-1].getArray(); + + OUString aIDValue = xAttribs->getValueByName( g_aIDAttr ); + if ( aIDValue.isEmpty() ) + throw css::xml::sax::SAXException(); // TODO: the ID value must present + + OUString aTypeValue = xAttribs->getValueByName( g_aTypeAttr ); + OUString aTargetValue = xAttribs->getValueByName( g_aTargetAttr ); + OUString aTargetModeValue = xAttribs->getValueByName( g_aTargetModeAttr ); + + pAttrs[++nAttrNum - 1].First = g_aIDAttr; + pAttrs[nAttrNum - 1].Second = aIDValue; + + if ( !aTypeValue.isEmpty() ) + { + pAttrs[++nAttrNum - 1].First = g_aTypeAttr; + pAttrs[nAttrNum - 1].Second = aTypeValue; + } + + if ( !aTargetValue.isEmpty() ) + { + pAttrs[++nAttrNum - 1].First = g_aTargetAttr; + pAttrs[nAttrNum - 1].Second = aTargetValue; + } + + if ( !aTargetModeValue.isEmpty() ) + { + pAttrs[++nAttrNum - 1].First = g_aTargetModeAttr; + pAttrs[nAttrNum - 1].Second = aTargetModeValue; + } + + pResultSeq[nNewEntryNum-1].realloc( nAttrNum ); + } + else + throw css::xml::sax::SAXException(); // TODO: no other elements expected! + } + else if ( m_nFormat == CONTENTTYPE_FORMAT ) + { + if ( aName == g_aTypesElement ) + { + sal_Int32 nNewLength = m_aElementsSeq.size() + 1; + + if ( nNewLength != 1 ) + throw css::xml::sax::SAXException(); // TODO: this element must be the first level element + + m_aElementsSeq.push_back( aName ); + + if ( !m_aResultSeq.hasElements() ) + m_aResultSeq.realloc( 2 ); + + return; // nothing to do + } + else if ( aName == g_aDefaultElement ) + { + sal_Int32 nNewLength = m_aElementsSeq.size() + 1; + if ( nNewLength != 2 ) + throw css::xml::sax::SAXException(); // TODO: this element must be the second level element + + m_aElementsSeq.push_back( aName ); + + if ( !m_aResultSeq.hasElements() ) + m_aResultSeq.realloc( 2 ); + + if ( m_aResultSeq.getLength() != 2 ) + throw uno::RuntimeException(); + + auto pResultSeq = m_aResultSeq.getArray(); + + const OUString aExtensionValue = xAttribs->getValueByName( g_aExtensionAttr ); + if ( aExtensionValue.isEmpty() ) + throw css::xml::sax::SAXException(); // TODO: the Extension value must present + + const OUString aContentTypeValue = xAttribs->getValueByName( g_aContentTypeAttr ); + if ( aContentTypeValue.isEmpty() ) + throw css::xml::sax::SAXException(); // TODO: the ContentType value must present + + const sal_Int32 nNewResultLen = m_aResultSeq[0].getLength() + 1; + pResultSeq[0].realloc( nNewResultLen ); + auto pSeq = pResultSeq[0].getArray(); + + pSeq[nNewResultLen-1].First = aExtensionValue; + pSeq[nNewResultLen-1].Second = aContentTypeValue; + } + else if ( aName == g_aOverrideElement ) + { + sal_Int32 nNewLength = m_aElementsSeq.size() + 1; + if ( nNewLength != 2 ) + throw css::xml::sax::SAXException(); // TODO: this element must be the second level element + + m_aElementsSeq.push_back( aName ); + + if ( !m_aResultSeq.hasElements() ) + m_aResultSeq.realloc( 2 ); + + if ( m_aResultSeq.getLength() != 2 ) + throw uno::RuntimeException(); + + auto pResultSeq = m_aResultSeq.getArray(); + + OUString aPartNameValue = xAttribs->getValueByName( g_aPartNameAttr ); + if ( aPartNameValue.isEmpty() ) + throw css::xml::sax::SAXException(); // TODO: the PartName value must present + + OUString aContentTypeValue = xAttribs->getValueByName( g_aContentTypeAttr ); + if ( aContentTypeValue.isEmpty() ) + throw css::xml::sax::SAXException(); // TODO: the ContentType value must present + + sal_Int32 nNewResultLen = m_aResultSeq[1].getLength() + 1; + pResultSeq[1].realloc( nNewResultLen ); + auto pSeq = pResultSeq[1].getArray(); + + pSeq[nNewResultLen-1].First = aPartNameValue; + pSeq[nNewResultLen-1].Second = aContentTypeValue; + } + else + throw css::xml::sax::SAXException(); // TODO: no other elements expected! + } + else + throw css::xml::sax::SAXException(); // TODO: no other elements expected! +} + + +void SAL_CALL OFOPXMLHelper_Impl::endElement( const OUString& aName ) +{ + if ( m_nFormat == RELATIONINFO_FORMAT || m_nFormat == CONTENTTYPE_FORMAT ) + { + sal_Int32 nLength = m_aElementsSeq.size(); + if ( nLength <= 0 ) + throw css::xml::sax::SAXException(); // TODO: no other end elements expected! + + if ( m_aElementsSeq[nLength-1] != aName ) + throw css::xml::sax::SAXException(); // TODO: unexpected element ended + + m_aElementsSeq.resize( nLength - 1 ); + } +} + + +void SAL_CALL OFOPXMLHelper_Impl::characters( const OUString& /*aChars*/ ) +{ +} + + +void SAL_CALL OFOPXMLHelper_Impl::ignorableWhitespace( const OUString& /*aWhitespaces*/ ) +{ +} + + +void SAL_CALL OFOPXMLHelper_Impl::processingInstruction( const OUString& /*aTarget*/, const OUString& /*aData*/ ) +{ +} + + +void SAL_CALL OFOPXMLHelper_Impl::setDocumentLocator( const uno::Reference< css::xml::sax::XLocator >& /*xLocator*/ ) +{ +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/comphelper/source/xml/xmltools.cxx b/comphelper/source/xml/xmltools.cxx new file mode 100644 index 000000000..1b10964b1 --- /dev/null +++ b/comphelper/source/xml/xmltools.cxx @@ -0,0 +1,99 @@ +/* -*- 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/. + */ + +#include <comphelper/xmltools.hxx> +#include <rtl/random.h> +#include <tools/Guid.hxx> +#include <vector> + +using namespace com::sun::star; + +namespace +{ + //Will be inside an xml comment, so can't use '-' in case '--' appears in + //output, etc. Despite what *is* legal in an xml comment, just using the + //base-64 subset to avoid pain with simplistic third-party parsers + const sal_uInt8 aChaffEncoder[] = + { + 'A', 'Q', 'g', 'w', 'B', 'R', 'h', 'x', + 'C', 'S', 'i', 'y', 'D', 'T', 'j', 'z', + 'E', 'U', 'k', '0', 'F', 'V', 'l', '1', + 'G', 'W', 'm', '2', 'H', 'X', 'n', '3', + 'I', 'Y', 'o', '4', 'J', 'Z', 'p', '5', + 'K', 'a', 'q', '6', 'L', 'b', 'r', '7', + 'M', 'c', 's', '8', 'N', 'd', 't', '9', + 'O', 'e', 'u', '+', 'P', 'f', 'v', '/', + + 'A', 'Q', 'g', 'w', 'B', 'R', 'h', 'x', + 'C', 'S', 'i', 'y', 'D', 'T', 'j', 'z', + 'E', 'U', 'k', '0', 'F', 'V', 'l', '1', + 'G', 'W', 'm', '2', 'H', 'X', 'n', '3', + 'I', 'Y', 'o', '4', 'J', 'Z', 'p', '5', + 'K', 'a', 'q', '6', 'L', 'b', 'r', '7', + 'M', 'c', 's', '8', 'N', 'd', 't', '9', + 'O', 'e', 'u', '+', 'P', 'f', 'v', '/', + + 'A', 'Q', 'g', 'w', 'B', 'R', 'h', 'x', + 'C', 'S', 'i', 'y', 'D', 'T', 'j', 'z', + 'E', 'U', 'k', '0', 'F', 'V', 'l', '1', + 'G', 'W', 'm', '2', 'H', 'X', 'n', '3', + 'I', 'Y', 'o', '4', 'J', 'Z', 'p', '5', + 'K', 'a', 'q', '6', 'L', 'b', 'r', '7', + 'M', 'c', 's', '8', 'N', 'd', 't', '9', + 'O', 'e', 'u', '+', 'P', 'f', 'v', '/', + + 'A', 'Q', 'g', 'w', 'B', 'R', 'h', 'x', + 'C', 'S', 'i', 'y', 'D', 'T', 'j', 'z', + 'E', 'U', 'k', '0', 'F', 'V', 'l', '1', + 'G', 'W', 'm', '2', 'H', 'X', 'n', '3', + 'I', 'Y', 'o', '4', 'J', 'Z', 'p', '5', + 'K', 'a', 'q', '6', 'L', 'b', 'r', '7', + 'M', 'c', 's', '8', 'N', 'd', 't', '9', + 'O', 'e', 'u', '+', 'P', 'f', 'v', '/' + }; + + void encodeChaff(std::vector<sal_uInt8> &rChaff) + { + static_assert(sizeof(aChaffEncoder) == 256, "this has to cover all chars"); + + for (auto & elem : rChaff) + { + elem = aChaffEncoder[elem]; + } + } +} + +namespace comphelper::xml +{ + OString makeXMLChaff() + { + rtlRandomPool pool = rtl_random_createPool(); + + sal_Int8 n; + rtl_random_getBytes(pool, &n, 1); + + sal_Int32 nLength = 1024+n; + // coverity[tainted_data] - 1024 deliberate random minus max -127/plus max 128 + std::vector<sal_uInt8> aChaff(nLength); + rtl_random_getBytes(pool, aChaff.data(), nLength); + + rtl_random_destroyPool(pool); + + encodeChaff(aChaff); + + return OString(reinterpret_cast<const char*>(aChaff.data()), nLength); + } + + OString generateGUIDString() + { + tools::Guid aGuid(tools::Guid::Generate); + return aGuid.getString(); + } +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |