/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace comphelper { using ::com::sun::star::uno::Reference; using ::com::sun::star::uno::XInterface; using ::com::sun::star::uno::UNO_QUERY; using ::com::sun::star::uno::RuntimeException; using ::com::sun::star::uno::Any; using ::com::sun::star::uno::Sequence; using ::com::sun::star::uno::Type; using ::com::sun::star::container::XEnumerableMap; using ::com::sun::star::lang::NoSupportException; using ::com::sun::star::beans::IllegalTypeException; using ::com::sun::star::container::NoSuchElementException; using ::com::sun::star::lang::IllegalArgumentException; using ::com::sun::star::lang::XInitialization; using ::com::sun::star::ucb::AlreadyInitializedException; using ::com::sun::star::beans::Pair; using ::com::sun::star::uno::TypeClass; using ::com::sun::star::uno::TypeClass_VOID; using ::com::sun::star::uno::TypeClass_UNKNOWN; using ::com::sun::star::uno::TypeClass_ANY; using ::com::sun::star::uno::TypeClass_EXCEPTION; using ::com::sun::star::uno::TypeClass_STRUCT; using ::com::sun::star::uno::TypeClass_FLOAT; using ::com::sun::star::uno::TypeClass_DOUBLE; using ::com::sun::star::uno::TypeClass_INTERFACE; using ::com::sun::star::lang::XServiceInfo; using ::com::sun::star::uno::XComponentContext; using ::com::sun::star::container::XEnumeration; using ::com::sun::star::uno::TypeDescription; using ::com::sun::star::lang::DisposedException; namespace { class MapEnumerator; } typedef std::map< Any, Any, LessPredicateAdapter > KeyedValues; namespace { struct MapData { Type m_aKeyType; Type m_aValueType; std::optional< KeyedValues > m_pValues; std::shared_ptr< IKeyPredicateLess > m_pKeyCompare; bool m_bMutable; std::vector< MapEnumerator* > m_aModListeners; MapData() :m_bMutable( true ) { } MapData( const MapData& _source ) :m_aKeyType( _source.m_aKeyType ) ,m_aValueType( _source.m_aValueType ) ,m_pKeyCompare( _source.m_pKeyCompare ) ,m_bMutable( false ) { m_pValues.emplace( *_source.m_pValues ); } private: MapData& operator=( const MapData& _source ) = delete; }; } static void lcl_registerMapModificationListener( MapData& _mapData, MapEnumerator& _listener ) { #if OSL_DEBUG_LEVEL > 0 for ( const MapEnumerator* lookup : _mapData.m_aModListeners ) { OSL_ENSURE( lookup != &_listener, "lcl_registerMapModificationListener: this listener is already registered!" ); } #endif _mapData.m_aModListeners.push_back( &_listener ); } static void lcl_revokeMapModificationListener( MapData& _mapData, MapEnumerator& _listener ) { auto lookup = std::find(_mapData.m_aModListeners.begin(), _mapData.m_aModListeners.end(), &_listener); if (lookup != _mapData.m_aModListeners.end()) { _mapData.m_aModListeners.erase( lookup ); return; } OSL_FAIL( "lcl_revokeMapModificationListener: the listener is not registered!" ); } static void lcl_notifyMapDataListeners_nothrow( const MapData& _mapData ); // EnumerableMap typedef ::cppu::WeakComponentImplHelper < XInitialization , XEnumerableMap , XServiceInfo > Map_IFace; namespace { class EnumerableMap: public Map_IFace, public ComponentBase { public: EnumerableMap(); protected: virtual ~EnumerableMap() override; // XInitialization virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) override; // XEnumerableMap virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createKeyEnumeration( sal_Bool Isolated ) override; virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createValueEnumeration( sal_Bool Isolated ) override; virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createElementEnumeration( sal_Bool Isolated ) override; // XMap virtual Type SAL_CALL getKeyType() override; virtual Type SAL_CALL getValueType() override; virtual void SAL_CALL clear( ) override; virtual sal_Bool SAL_CALL containsKey( const Any& _key ) override; virtual sal_Bool SAL_CALL containsValue( const Any& _value ) override; virtual Any SAL_CALL get( const Any& _key ) override; virtual Any SAL_CALL put( const Any& _key, const Any& _value ) override; virtual Any SAL_CALL remove( const Any& _key ) override; // XElementAccess (base of XMap) virtual Type SAL_CALL getElementType() override; virtual sal_Bool SAL_CALL hasElements() override; // XServiceInfo virtual OUString SAL_CALL getImplementationName( ) override; virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; private: void impl_initValues_throw( const Sequence< Pair< Any, Any > >& _initialValues ); /// throws an IllegalTypeException if the given value is not compatible with our ValueType void impl_checkValue_throw( const Any& _value ) const; void impl_checkKey_throw( const Any& _key ) const; void impl_checkNaN_throw( const Any& _keyOrValue, const Type& _keyOrValueType ) const; void impl_checkMutable_throw() const; private: ::osl::Mutex m_aMutex; MapData m_aData; }; enum EnumerationType { eKeys, eValues, eBoth }; class MapEnumerator final { public: MapEnumerator( ::cppu::OWeakObject& _rParent, MapData& _mapData, const EnumerationType _type ) :m_rParent( _rParent ) ,m_rMapData( _mapData ) ,m_eType( _type ) ,m_mapPos( _mapData.m_pValues->begin() ) ,m_disposed( false ) { lcl_registerMapModificationListener( m_rMapData, *this ); } ~MapEnumerator() { dispose(); } void dispose() { if ( !m_disposed ) { lcl_revokeMapModificationListener( m_rMapData, *this ); m_disposed = true; } } // noncopyable MapEnumerator(const MapEnumerator&) = delete; const MapEnumerator& operator=(const MapEnumerator&) = delete; // XEnumeration equivalents bool hasMoreElements(); Any nextElement(); /// called when the map was modified void mapModified(); private: ::cppu::OWeakObject& m_rParent; MapData& m_rMapData; const EnumerationType m_eType; KeyedValues::const_iterator m_mapPos; bool m_disposed; }; } static void lcl_notifyMapDataListeners_nothrow( const MapData& _mapData ) { for ( MapEnumerator* loop : _mapData.m_aModListeners ) { loop->mapModified(); } } typedef ::cppu::WeakImplHelper < XEnumeration > MapEnumeration_Base; namespace { class MapEnumeration :public ComponentBase ,public MapEnumeration_Base { public: MapEnumeration( ::cppu::OWeakObject& _parentMap, MapData& _mapData, ::cppu::OBroadcastHelper& _rBHelper, const EnumerationType _type, const bool _isolated ) :ComponentBase( _rBHelper, ComponentBase::NoInitializationNeeded() ) ,m_xKeepMapAlive( _parentMap ) ,m_pMapDataCopy( _isolated ? new MapData( _mapData ) : nullptr ) ,m_aEnumerator( *this, _isolated ? *m_pMapDataCopy : _mapData, _type ) { } // XEnumeration virtual sal_Bool SAL_CALL hasMoreElements( ) override; virtual Any SAL_CALL nextElement( ) override; protected: virtual ~MapEnumeration() override { acquire(); { ::osl::MutexGuard aGuard( getMutex() ); m_aEnumerator.dispose(); m_pMapDataCopy.reset(); } } private: // since we share our mutex with the main map, we need to keep it alive as long as we live Reference< XInterface > m_xKeepMapAlive; std::unique_ptr< MapData > m_pMapDataCopy; MapEnumerator m_aEnumerator; }; } EnumerableMap::EnumerableMap() :Map_IFace( m_aMutex ) ,ComponentBase( Map_IFace::rBHelper ) { } EnumerableMap::~EnumerableMap() { if ( !impl_isDisposed() ) { acquire(); dispose(); } } void SAL_CALL EnumerableMap::initialize( const Sequence< Any >& _arguments ) { ComponentMethodGuard aGuard( *this, ComponentMethodGuard::MethodType::WithoutInit ); if ( impl_isInitialized_nothrow() ) throw AlreadyInitializedException(); sal_Int32 nArgumentCount = _arguments.getLength(); if ( ( nArgumentCount != 2 ) && ( nArgumentCount != 3 ) ) throw IllegalArgumentException(u"wrong number of args"_ustr, static_cast(this), 1); Type aKeyType, aValueType; if ( !( _arguments[0] >>= aKeyType ) ) throw IllegalArgumentException(u"com.sun.star.uno.Type expected."_ustr, *this, 1 ); if ( !( _arguments[1] >>= aValueType ) ) throw IllegalArgumentException(u"com.sun.star.uno.Type expected."_ustr, *this, 2 ); Sequence< Pair< Any, Any > > aInitialValues; bool bMutable = true; if ( nArgumentCount == 3 ) { if ( !( _arguments[2] >>= aInitialValues ) ) throw IllegalArgumentException(u"[]com.sun.star.beans.Pair expected."_ustr, *this, 2 ); bMutable = false; } // for the value, anything is allowed, except VOID if ( ( aValueType.getTypeClass() == TypeClass_VOID ) || ( aValueType.getTypeClass() == TypeClass_UNKNOWN ) ) throw IllegalTypeException(u"Unsupported value type."_ustr, *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(u"Unsupported key type."_ustr, *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 IllegalTypeException("EnumerableMap m_aData container is invalid or not empty.", *this); } for (auto& mapping : _initialValues) { 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( u"NaN (not-a-number) not supported by this implementation."_ustr, *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( u"NULL keys not supported by this implementation."_ustr, *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( u"The map is immutable."_ustr, *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 u"org.openoffice.comp.comphelper.EnumerableMap"_ustr; } sal_Bool SAL_CALL EnumerableMap::supportsService( const OUString& _serviceName ) { return cppu::supportsService(this, _serviceName); } Sequence< OUString > SAL_CALL EnumerableMap::getSupportedServiceNames( ) { return { u"com.sun.star.container.EnumerableMap"_ustr }; } 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(u"No more elements."_ustr, 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 const&) { return cppu::acquire(new comphelper::EnumerableMap()); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */