Program Listing for File array_message.hpp

Return to documentation for file (include/ros_babel_fish/messages/array_message.hpp)

// Copyright (c) 2021 Stefan Fabian. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#ifndef ROS_BABEL_FISH_ARRAY_MESSAGE_HPP
#define ROS_BABEL_FISH_ARRAY_MESSAGE_HPP

#include "message_type_traits.hpp"
#include "ros_babel_fish/exceptions/babel_fish_exception.hpp"
#include "ros_babel_fish/messages/compound_message.hpp"

#include <rclcpp/time.hpp>
#include <rosidl_runtime_cpp/bounded_vector.hpp>

#include <memory>
#include <regex>

namespace ros_babel_fish
{
class ArrayMessageBase : public Message
{
public:
  RCLCPP_SMART_PTR_DEFINITIONS_NOT_COPYABLE( ArrayMessageBase )

  ArrayMessageBase( MessageMemberIntrospection member, std::shared_ptr<void> data )
      : Message( MessageTypes::Array, std::move( data ) ), member_( std::move( member ) )
  {
  }

  bool isFixedSize() const { return member_->array_size_ != 0 && !member_->is_upper_bound_; }

  bool isBounded() const { return member_->is_upper_bound_; }

  MessageType elementType() const { return MessageType( member_->type_id_ ); }

  virtual size_t size() const { return member_->size_function( data_.get() ); }

  size_t maxSize() const { return member_->array_size_; }

  ArrayMessageBase &operator=( const ArrayMessageBase &other )
  {
    if ( this == &other )
      return *this;
    if ( elementType() != other.elementType() )
      throw BabelFishException( std::string( "Incompatible array types! (" ) +
                                std::to_string( elementType() ) + " vs " +
                                std::to_string( other.elementType() ) + ")" );
    _assign( other );
    return *this;
  }

  MessageMemberIntrospection elementIntrospection() const { return member_; }

protected:
  // Disable copy construction except for subclasses
  ArrayMessageBase( const ArrayMessageBase &other ) = default;

  void _assign( const Message &other ) override
  {
    if ( other.type() != MessageTypes::Array )
      throw BabelFishException( "Tried to assign non-array message to array message!" );
    *this = static_cast<const ArrayMessageBase &>( other );
  }

  virtual void _assign( const ArrayMessageBase &other ) = 0;

  MessageMemberIntrospection member_;
};

template<typename T, bool BOUNDED, bool FIXED_LENGTH>
class ArrayMessage_ final : public ArrayMessageBase
{
protected:
  typedef typename message_type_traits::array_type<T, FIXED_LENGTH>::Reference Reference;
  typedef typename message_type_traits::array_type<T, FIXED_LENGTH>::ReturnType ReturnType;
  typedef typename message_type_traits::array_type<T, FIXED_LENGTH>::ConstReturnType ConstReturnType;
  typedef typename message_type_traits::array_type<T, FIXED_LENGTH>::ArgumentType ArgumentType;
  static_assert( ( FIXED_LENGTH && BOUNDED ) || !FIXED_LENGTH,
                 "Fixed length can only be true if bounded is also true!" );

public:
  RCLCPP_SMART_PTR_DEFINITIONS( ArrayMessage_<T, BOUNDED, FIXED_LENGTH> )

  ArrayMessage_( const ArrayMessage_ & ) = delete;

  explicit ArrayMessage_( MessageMemberIntrospection member, std::shared_ptr<void> data )
      : ArrayMessageBase( std::move( member ), std::move( data ) )
  {
  }

  ~ArrayMessage_() override = default;

  // Explicitly use Message operator to prevent hidden warnings
  using Message::operator[];

  template<typename ENABLED = T>
  typename std::enable_if<FIXED_LENGTH || !std::is_same<ENABLED, bool>::value, Reference>::type
  operator[]( size_t index )
  {
    using Container =
        typename std::conditional<FIXED_LENGTH, std::array<T, 987654321000>, std::vector<T>>::type;
    if ( member_->get_function == nullptr ) {
      if ( index >= size() )
        throw std::out_of_range( "Index was out of range of array!" );
      return ( *reinterpret_cast<Container *>( data_.get() ) )[index];
    }
    return *reinterpret_cast<ReturnType *>( member_->get_function( data_.get(), index ) );
  }

  template<typename ENABLED = T>
  typename std::enable_if<!FIXED_LENGTH && std::is_same<ENABLED, bool>::value, Reference>::type
  operator[]( size_t index )
  {
    // Need to specialize for bool because std::vector<bool> is specialized and uses one bit per
    // bool, hence, you can not return a bool reference but need to use std::_Bit_reference
    if ( index >= size() )
      throw std::out_of_range( "Index was out of range of array!" );
    return ( *reinterpret_cast<std::vector<T> *>( data_.get() ) )[index];
  }

  ConstReturnType operator[]( size_t index ) const
  {
    using Container =
        typename std::conditional<FIXED_LENGTH, std::array<T, 987654321000>, std::vector<T>>::type;
    if ( member_->get_function == nullptr ) {
      if ( index >= size() )
        throw std::out_of_range( "Index was out of range of array!" );
      return ( *reinterpret_cast<const Container *>( data_.get() ) )[index];
    }
    return *reinterpret_cast<const ConstReturnType *>(
        member_->get_const_function( data_.get(), index ) );
  }

  Reference at( size_t index ) { return operator[]( index ); }

  ConstReturnType at( size_t index ) const { return operator[]( index ); }

  void assign( size_t index, ArgumentType value ) { ( *this )[index] = value; }

  template<bool ENABLED = FIXED_LENGTH>
  typename std::enable_if<ENABLED, void>::type fill( ArgumentType &value )
  {
    for ( size_t i = 0; i < maxSize(); ++i ) assign( i, value );
  }

  void replace( size_t index, ArgumentType value ) { assign( index, value ); }

  void push_back( ArgumentType value )
  {
    if ( BOUNDED ) {
      if ( FIXED_LENGTH )
        throw std::length_error( "Can not push back on fixed length array!" );
      // This means it is upper bound, otherwise the method would not be enabled / available
      if ( member_->array_size_ <= member_->size_function( data_.get() ) )
        throw std::length_error( "Exceeded upper bound!" );
    }
    reinterpret_cast<std::vector<T> *>( data_.get() )->push_back( value );
  }

  void append( ArgumentType value ) { push_back( value ); }

  void pop_back()
  {
    if ( size() == 0 )
      return;
    if ( FIXED_LENGTH )
      throw std::length_error( "Can not pop_back fixed length array!" );
    resize( size() - 1 );
  }

  void resize( size_t length )
  {
    if ( BOUNDED ) {
      if ( FIXED_LENGTH )
        throw std::length_error( "Can not resize fixed length array!" );
      // This means it is upper bound, otherwise the method would not be enabled / available
      if ( member_->array_size_ < length )
        throw std::length_error( "Exceeded upper bound!" );
    }
    if ( member_->resize_function == nullptr )
      reinterpret_cast<std::vector<T> *>( data_.get() )->resize( length );
    else
      member_->resize_function( data_.get(), length );
  }

  size_t size() const override
  {
    if ( FIXED_LENGTH )
      return member_->array_size_;

    if ( member_->size_function == nullptr )
      return reinterpret_cast<std::vector<T> *>( data_.get() )->size();
    return member_->size_function( data_.get() );
  }

  void clear()
  {
    if ( FIXED_LENGTH ) {
      throw BabelFishException( "Can not clear fixed length array!" );
    } else {
      resize( 0 );
    }
  }

protected:
  void _assign( const ArrayMessageBase &other ) override
  {
    if ( other.isBounded() ) {
      if ( other.isFixedSize() ) {
        _assignImpl<true, true>( other );
        return;
      }
      _assignImpl<true, false>( other );
      return;
    }
    _assignImpl<false, false>( other );
  }

  template<bool B, bool FL>
  void _assignImpl( const ArrayMessageBase &other )
  {
    auto &other_typed = dynamic_cast<const ArrayMessage_<T, B, FL> &>( other );
    if ( BOUNDED && other.size() > maxSize() ) {
      throw std::out_of_range(
          "Can not assign ArrayMessage as it exceeds the maximum size of this ArrayMessage!" );
    }
    if ( !FIXED_LENGTH )
      resize( other.size() );
    for ( size_t index = 0; index < other.size(); ++index ) at( index ) = other_typed.at( index );
  }

  bool _isMessageEqual( const Message &o ) const override
  {
    const auto &other = o.as<ArrayMessageBase>();
    if ( other.isBounded() ) {
      if ( other.isFixedSize() ) {
        return _isMessageEqualImpl<true, true>( other );
      }
      return _isMessageEqualImpl<true, false>( other );
    }
    return _isMessageEqualImpl<false, false>( other );
  }

  template<bool B, bool FL>
  bool _isMessageEqualImpl( const ArrayMessageBase &other ) const
  {
    auto &other_typed = dynamic_cast<const ArrayMessage_<T, B, FL> &>( other );
    if ( size() != other.size() )
      return false;
    for ( size_t index = 0; index < size(); ++index ) {
      if ( at( index ) != other_typed.at( index ) )
        return false;
    }
    return true;
  }
};

template<typename T>
using ArrayMessage = ArrayMessage_<T, false, false>;

template<typename T>
using FixedLengthArrayMessage = ArrayMessage_<T, true, true>;

template<typename T>
using BoundedArrayMessage = ArrayMessage_<T, true, false>;

template<bool BOUNDED, bool FIXED_LENGTH>
class CompoundArrayMessage_ final : public ArrayMessageBase
{
  static_assert( ( FIXED_LENGTH && BOUNDED ) || !FIXED_LENGTH,
                 "Fixed length can only be true if bounded is also true!" );

public:
  RCLCPP_SMART_PTR_DEFINITIONS( CompoundArrayMessage_<BOUNDED, FIXED_LENGTH> )


  explicit CompoundArrayMessage_( MessageMemberIntrospection member, std::shared_ptr<void> data )
      : ArrayMessageBase( std::move( member ), std::move( data ) )
  {
    values_.resize( member_->size_function( data_.get() ) );
  }

  ~CompoundArrayMessage_() override { }

  std::string elementDatatype() const
  {
    MessageMembersIntrospection introspection( member_ );
    return std::string( introspection->message_namespace_ ) + "::" + introspection->message_name_;
  }

  std::string elementName() const
  {
    static const std::regex namespace_regex( "::" );
    return std::regex_replace( elementDatatype(), namespace_regex, "/" );
  }

  // Explicitly use Message operator to prevent hidden warnings
  using Message::operator[];

  CompoundMessage &operator[]( size_t index ) { return getImplementation( index ); }

  const CompoundMessage &operator[]( size_t index ) const { return getImplementation( index ); }

  CompoundMessage &at( size_t index ) { return getImplementation( index ); }

  const CompoundMessage &at( size_t index ) const { return getImplementation( index ); }

  virtual void assign( size_t index, const CompoundMessage &value )
  {
    getImplementation( index ) = value;
  }

  void replace( size_t index, const CompoundMessage &value ) { assign( index, value ); }

  void push_back( const CompoundMessage &value )
  {
    if ( BOUNDED ) {
      if ( FIXED_LENGTH )
        throw std::length_error( "Can not push back on fixed length array!" );
      if ( member_->array_size_ <= member_->size_function( data_.get() ) )
        throw std::length_error( "Exceeded upper bound!" );
    }
    const size_t index = size();
    resize( index + 1 );
    assign( index, value );
  }

  void append( const CompoundMessage &value ) { push_back( value ); }

  CompoundMessage &appendEmpty()
  {
    size_t index = size();
    resize( index + 1 );
    return getImplementation( index );
  }

  void pop_back()
  {
    if ( size() == 0 )
      return;
    if ( FIXED_LENGTH )
      throw std::length_error( "Can not pop_back fixed length array!" );
    resize( size() - 1 );
  }

  void resize( size_t length )
  {
    if ( length == values_.size() )
      return;
    if ( BOUNDED ) {
      if ( FIXED_LENGTH )
        throw std::length_error( "Can not resize fixed length array!" );
      if ( member_->array_size_ < length )
        throw std::length_error( "Exceeded upper bound!" );
    }
    member_->resize_function( data_.get(), length );
    values_.resize( length );
    // Container may have reallocated -> content could be moved
    for ( size_t i = 0; i < values_.size(); ++i ) {
      if ( values_[i] == nullptr )
        continue;
      void *p = member_->get_function( data_.get(), i );
      if ( p == values_[i]->data_.get() )
        return; // Content was not moved
      std::shared_ptr<void> data( p, [parent = data_]( void * ) { (void)parent; } );
      values_[i]->move( data );
    }
  }

  std::vector<CompoundMessage::SharedPtr> values()
  {
    ensureInitialized();
    return values_;
  }

  std::vector<CompoundMessage::ConstSharedPtr> values() const
  {
    ensureInitialized();
    return { values_.begin(), values_.end() };
  }

  template<bool B, bool FL>
  CompoundArrayMessage_<BOUNDED, FIXED_LENGTH> &operator=( const CompoundArrayMessage_<B, FL> &other )
  {
    if ( this == &other )
      return *this;
    _assignImpl<B, FL>( other );
    return *this;
  }

  void clear()
  {
    if ( FIXED_LENGTH ) {
      throw BabelFishException( "Can not clear fixed length array!" );
    } else {
      member_->resize_function( data_.get(), 0 );
      values_.clear();
    }
  }

protected:
  void onMoved() override
  {
    for ( size_t i = 0; i < values_.size(); ++i ) {
      if ( values_[i] == nullptr )
        continue;
      void *p = member_->get_function( data_.get(), i );
      if ( p == values_[i]->data_.get() )
        continue; // No need to move
      std::shared_ptr<void> data( p, [parent = data_]( void * ) { (void)parent; } );
      values_[i]->move( data );
    }
  }

private:
  void ensureInitialized() const
  {
    for ( int index = 0; index < values_.size(); ++index ) {
      if ( values_[index] != nullptr )
        continue;

      void *p = member_->get_function( data_.get(), index );
      std::shared_ptr<void> data( p, [parent = data_]( void * ) { (void)parent; } );
      values_[index] = CompoundMessage::make_shared( member_, std::move( data ) );
    }
  }

  void ensureInitialized( size_t index ) const
  {
    if ( index >= values_.size() ) {
      size_t size = member_->size_function( data_.get() );
      if ( index >= size )
        throw std::out_of_range( "Index was out of range of compound array!" );
      values_.resize( size );
    }
    if ( values_[index] == nullptr ) {
      void *p = member_->get_function( data_.get(), index );
      std::shared_ptr<void> data( p, [parent = data_]( void * ) { (void)parent; } );
      values_[index] = CompoundMessage::make_shared( member_, std::move( data ) );
    }
  }

  CompoundMessage &getImplementation( size_t index )
  {
    ensureInitialized( index );
    return *values_[index];
  }

  const CompoundMessage &getImplementation( size_t index ) const
  {
    ensureInitialized( index );
    return *values_[index];
  }

  void _assign( const ArrayMessageBase &other ) override
  {
    if ( other.isBounded() ) {
      if ( other.isFixedSize() ) {
        _assignImpl<true, true>( other );
        return;
      }
      _assignImpl<true, false>( other );
      return;
    }
    _assignImpl<false, false>( other );
  }

  template<bool B, bool FL>
  void _assignImpl( const ArrayMessageBase &other )
  {
    auto &other_typed = static_cast<const CompoundArrayMessage_<B, FL> &>( other );
    if ( BOUNDED && other.size() > maxSize() ) {
      throw std::out_of_range( "Can not assign CompoundArrayMessage as it exceeds the maximum size "
                               "of this CompoundArrayMessage!" );
    }
    if ( !FIXED_LENGTH )
      resize( other.size() );
    for ( size_t index = 0; index < other.size(); ++index ) at( index ) = other_typed.at( index );
  }

  bool _isMessageEqual( const Message &o ) const override
  {
    const auto &other = o.as<ArrayMessageBase>();
    if ( other.isBounded() ) {
      if ( other.isFixedSize() ) {
        return _isMessageEqualImpl<true, true>( other );
      }
      return _isMessageEqualImpl<true, false>( other );
    }
    return _isMessageEqualImpl<false, false>( other );
  }

  template<bool B, bool FL>
  bool _isMessageEqualImpl( const ArrayMessageBase &other ) const
  {
    auto &other_typed = dynamic_cast<const CompoundArrayMessage_<B, FL> &>( other );
    if ( size() != other.size() )
      return false;
    for ( size_t index = 0; index < size(); ++index ) {
      if ( at( index ) != other_typed.at( index ) )
        return false;
    }
    return true;
  }

  mutable std::vector<CompoundMessage::SharedPtr> values_;
};

using CompoundArrayMessage = CompoundArrayMessage_<false, false>;

using FixedLengthCompoundArrayMessage = CompoundArrayMessage_<true, true>;

using BoundedCompoundArrayMessage = CompoundArrayMessage_<true, false>;
} // namespace ros_babel_fish

#endif // ROS_BABEL_FISH_ARRAY_MESSAGE_HPP