/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <comphelper/anytostring.hxx>
#include <comphelper/anycompare.hxx>
#include <comphelper/componentbase.hxx>
#include <com/sun/star/container/XEnumerableMap.hpp>
#include <com/sun/star/lang/NoSupportException.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/ucb/AlreadyInitializedException.hpp>
#include <com/sun/star/beans/IllegalTypeException.hpp>
#include <com/sun/star/beans/Pair.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <cppuhelper/compbase.hxx>
#include <cppuhelper/implbase.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <typelib/typedescription.hxx>
#include <cmath>
#include <map>
#include <memory>
#include <optional>
#include <utility>
namespace comphelper
{
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::XInterface;
using ::com::sun::star::uno::UNO_QUERY;
using ::com::sun::star::uno::RuntimeException;
using ::com::sun::star::uno::Any;
using ::com::sun::star::uno::Sequence;
using ::com::sun::star::uno::Type;
using ::com::sun::star::container::XEnumerableMap;
using ::com::sun::star::lang::NoSupportException;
using ::com::sun::star::beans::IllegalTypeException;
using ::com::sun::star::container::NoSuchElementException;
using ::com::sun::star::lang::IllegalArgumentException;
using ::com::sun::star::lang::XInitialization;
using ::com::sun::star::ucb::AlreadyInitializedException;
using ::com::sun::star::beans::Pair;
using ::com::sun::star::uno::TypeClass;
using ::com::sun::star::uno::TypeClass_VOID;
using ::com::sun::star::uno::TypeClass_UNKNOWN;
using ::com::sun::star::uno::TypeClass_ANY;
using ::com::sun::star::uno::TypeClass_EXCEPTION;
using ::com::sun::star::uno::TypeClass_STRUCT;
using ::com::sun::star::uno::TypeClass_FLOAT;
using ::com::sun::star::uno::TypeClass_DOUBLE;
using ::com::sun::star::uno::TypeClass_INTERFACE;
using ::com::sun::star::lang::XServiceInfo;
using ::com::sun::star::uno::XComponentContext;
using ::com::sun::star::container::XEnumeration;
using ::com::sun::star::uno::TypeDescription;
using ::com::sun::star::lang::DisposedException;
namespace {
class MapEnumerator;
}
typedef std::map< Any, Any, LessPredicateAdapter > KeyedValues;
namespace {
struct MapData
{
Type m_aKeyType;
Type m_aValueType;
std::optional< KeyedValues > m_pValues;
std::shared_ptr< IKeyPredicateLess > m_pKeyCompare;
bool m_bMutable;
std::vector< MapEnumerator* > m_aModListeners;
MapData()
:m_bMutable( true )
{
}
MapData( const MapData& _source )
:m_aKeyType( _source.m_aKeyType )
,m_aValueType( _source.m_aValueType )
,m_pKeyCompare( _source.m_pKeyCompare )
,m_bMutable( false )
{
m_pValues.emplace( *_source.m_pValues );
}
private:
MapData& operator=( const MapData& _source ) = delete;
};
}
static void lcl_registerMapModificationListener( MapData& _mapData, MapEnumerator& _listener )
{
#if OSL_DEBUG_LEVEL > 0
for ( const MapEnumerator* lookup : _mapData.m_aModListeners )
{
OSL_ENSURE( lookup != &_listener, "lcl_registerMapModificationListener: this listener is already registered!" );
}
#endif
_mapData.m_aModListeners.push_back( &_listener );
}
static void lcl_revokeMapModificationListener( MapData& _mapData, MapEnumerator& _listener )
{
auto lookup = std::find(_mapData.m_aModListeners.begin(), _mapData.m_aModListeners.end(), &_listener);
if (lookup != _mapData.m_aModListeners.end())
{
_mapData.m_aModListeners.erase( lookup );
return;
}
OSL_FAIL( "lcl_revokeMapModificationListener: the listener is not registered!" );
}
static void lcl_notifyMapDataListeners_nothrow( const MapData& _mapData );
// EnumerableMap
typedef ::cppu::WeakComponentImplHelper < XInitialization
, XEnumerableMap
, XServiceInfo
> Map_IFace;
namespace {
class EnumerableMap: public Map_IFace, public ComponentBase
{
public:
EnumerableMap();
protected:
virtual ~EnumerableMap() override;
// XInitialization
virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) override;
// XEnumerableMap
virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createKeyEnumeration( sal_Bool Isolated ) override;
virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createValueEnumeration( sal_Bool Isolated ) override;
virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createElementEnumeration( sal_Bool Isolated ) override;
// XMap
virtual Type SAL_CALL getKeyType() override;
virtual Type SAL_CALL getValueType() override;
virtual void SAL_CALL clear( ) override;
virtual sal_Bool SAL_CALL containsKey( const Any& _key ) override;
virtual sal_Bool SAL_CALL containsValue( const Any& _value ) override;
virtual Any SAL_CALL get( const Any& _key ) override;
virtual Any SAL_CALL put( const Any& _key, const Any& _value ) override;
virtual Any SAL_CALL remove( const Any& _key ) override;
// XElementAccess (base of XMap)
virtual Type SAL_CALL getElementType() override;
virtual sal_Bool SAL_CALL hasElements() override;
// XServiceInfo
virtual OUString SAL_CALL getImplementationName( ) override;
virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
private:
void impl_initValues_throw( const Sequence< Pair< Any, Any > >& _initialValues );
/// throws an IllegalTypeException if the given value is not compatible with our ValueType
void impl_checkValue_throw( const Any& _value ) const;
void impl_checkKey_throw( const Any& _key ) const;
void impl_checkNaN_throw( const Any& _keyOrValue, const Type& _keyOrValueType ) const;
void impl_checkMutable_throw() const;
private:
::osl::Mutex m_aMutex;
MapData m_aData;
};
enum EnumerationType
{
eKeys, eValues, eBoth
};
class MapEnumerator final
{
public:
MapEnumerator( ::cppu::OWeakObject& _rParent, MapData& _mapData, const EnumerationType _type )
:m_rParent( _rParent )
,m_rMapData( _mapData )
,m_eType( _type )
,m_mapPos( _mapData.m_pValues->begin() )
,m_disposed( false )
{
lcl_registerMapModificationListener( m_rMapData, *this );
}
~MapEnumerator()
{
dispose();
}
void dispose()
{
if ( !m_disposed )
{
lcl_revokeMapModificationListener( m_rMapData, *this );
m_disposed = true;
}
}
// noncopyable
MapEnumerator(const MapEnumerator&) = delete;
const MapEnumerator& operator=(const MapEnumerator&) = delete;
// XEnumeration equivalents
bool hasMoreElements();
Any nextElement();
/// called when the map was modified
void mapModified();
private:
::cppu::OWeakObject& m_rParent;
MapData& m_rMapData;
const EnumerationType m_eType;
KeyedValues::const_iterator m_mapPos;
bool m_disposed;
};
}
static void lcl_notifyMapDataListeners_nothrow( const MapData& _mapData )
{
for ( MapEnumerator* loop : _mapData.m_aModListeners )
{
loop->mapModified();
}
}
typedef ::cppu::WeakImplHelper < XEnumeration
> MapEnumeration_Base;
namespace {
class MapEnumeration :public ComponentBase
,public MapEnumeration_Base
{
public:
MapEnumeration( ::cppu::OWeakObject& _parentMap, MapData& _mapData, ::cppu::OBroadcastHelper& _rBHelper,
const EnumerationType _type, const bool _isolated )
:ComponentBase( _rBHelper, ComponentBase::NoInitializationNeeded() )
,m_xKeepMapAlive( _parentMap )
,m_pMapDataCopy( _isolated ? new MapData( _mapData ) : nullptr )
,m_aEnumerator( *this, _isolated ? *m_pMapDataCopy : _mapData, _type )
{
}
// XEnumeration
virtual sal_Bool SAL_CALL hasMoreElements( ) override;
virtual Any SAL_CALL nextElement( ) override;
protected:
virtual ~MapEnumeration() override
{
acquire();
{
::osl::MutexGuard aGuard( getMutex() );
m_aEnumerator.dispose();
m_pMapDataCopy.reset();
}
}
private:
// since we share our mutex with the main map, we need to keep it alive as long as we live
Reference< XInterface > m_xKeepMapAlive;
std::unique_ptr< MapData > m_pMapDataCopy;
MapEnumerator m_aEnumerator;
};
}
EnumerableMap::EnumerableMap()
:Map_IFace( m_aMutex )
,ComponentBase( Map_IFace::rBHelper )
{
}
EnumerableMap::~EnumerableMap()
{
if ( !impl_isDisposed() )
{
acquire();
dispose();
}
}
void SAL_CALL EnumerableMap::initialize( const Sequence< Any >& _arguments )
{
ComponentMethodGuard aGuard( *this, ComponentMethodGuard::MethodType::WithoutInit );
if ( impl_isInitialized_nothrow() )
throw AlreadyInitializedException();
sal_Int32 nArgumentCount = _arguments.getLength();
if ( ( nArgumentCount != 2 ) && ( nArgumentCount != 3 ) )
throw IllegalArgumentException(u"wrong number of args"_ustr, static_cast<cppu::OWeakObject*>(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<any,any> 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 = std::move(aKeyType);
m_aData.m_aValueType = std::move(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("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<css::uno::Any> const&)
{
return cppu::acquire(new comphelper::EnumerableMap());
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V1053 Calling the 'acquire' virtual function in the destructor may lead to unexpected result at runtime.