// Copyright 2016-2023 Bloomberg Finance L.P.
// SPDX-License-Identifier: Apache-2.0
//
// Licensed 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
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// bmqa_messageproperties.h                                           -*-C++-*-
#ifndef INCLUDED_BMQA_MESSAGEPROPERTIES
#define INCLUDED_BMQA_MESSAGEPROPERTIES

//@PURPOSE: Provide a VST representing message properties.
//
//@CLASSES:
//  bmqa::MessageProperties:         VST representing message properties.
//  bmqa::MessagePropertiesIterator: Mechanism to iterate over properties.
//
//@SEE ALSO: bmqt::PropertyType
//
//@DESCRIPTION: 'bmqa::MessageProperties' provides a VST representing message
// properties.  Message properties are a collection of name-value pairs that
// producer can associate with a message, and consumer can retrieve from the
// corresponding message.  In order to keep their usage flexible, no schema is
// enforced for the message properties, and their format (names and data types)
// should be negotiated by producers and consumers.  Message properties can be
// used for routing, pipelining or filtering messages within the application.
// It can be efficient to specify such message attributes in the properties
// instead of the message payload, because application does not have to decode
// entire payload to retrieve these attributes.
// 'bmqa::MessagePropertiesIterator' provides a mechanism to iterate over all
// the properties of a 'bmqa::MessageProperties' object.
//
///Restrictions on Property Names
///------------------------------
//
//: o Length of a property name must be greater than zero and must *not* exceed
//:   'bmqa::MessageProperties::k_MAX_PROPERTY_NAME_LENGTH'
//
//: o First character of the property name must be alpha-numeric.
//
///Restrictions on Property Values
///-------------------------------
//
//: o Length of a property value must be non-negative (ie, can be zero) and
//:   must *not* exceed 'bmqa::MessageProperties::k_MAX_PROPERTY_VALUE_LENGTH'.
//:   Note that this restriction is obviously applicable to property values
//:   with types 'bmqt::PropertyType::e_STRING' and
//:   'bmqt::PropertyType::e_BINARY', because for all other property value
//:   types, size is implicitly applicable based on the type (see 'Data Types
//:   and Size' section in 'bmqt::PropertyType' component).
//


// BMQ
#include <bmqscm_version.h>
#include <bmqt_resultcode.h>
#include <bmqt_propertytype.h>

// BDE
#include <bdlbb_blob.h>
#include <bsl_string.h>
#include <bslma_allocator.h>
#include <bslma_usesbslmaallocator.h>
#include <bslmf_nestedtraitdeclaration.h>
#include <bsls_alignedbuffer.h>
#include <bsls_types.h>
#include <bsl_cstdint.h>


namespace BloombergLP {

// FORWARD DECLARATION
namespace bmqp { class MessageProperties; }
namespace bmqp { class MessagePropertiesIterator; }

namespace bmqa {

                          // =======================
                          // class MessageProperties
                          // =======================

class MessageProperties {
    // Provide a VST representing message properties.

    // FRIENDS
    friend class MessagePropertiesIterator;

  private:
    // PRIVATE CONSTANTS
    static const int k_MAX_SIZEOF_BMQP_MESSAGEPROPERTIES = 176;
        // Constant representing the maximum size of a
        // 'bmqp::MessageProperties' object, so that the below AlignedBuffer
        // is big enough.

    // PRIVATE TYPES
    typedef
    bsls::AlignedBuffer<k_MAX_SIZEOF_BMQP_MESSAGEPROPERTIES> ImplBuffer;

  private:
    // DATA
    mutable bmqp::MessageProperties *d_impl_p;
                                        // Pointer to the implementation object
                                        // in 'd_buffer', providing a shortcut
                                        // type safe cast to that object.  This
                                        // variable *must* *be* the first
                                        // member of this class, as other
                                        // components in bmqa package may
                                        // reinterpret_cast to that variable.

    ImplBuffer                       d_buffer;
                                        // Buffer containing the implementation
                                        // object, maximally aligned.

    bslma::Allocator                *d_allocator_p;

  public:
    // PUBLIC CONSTANTS
    static const int k_MAX_NUM_PROPERTIES        = 255;
        // Maximum number of properties that can appear in a 'bmqa::Message'.

    static const int k_MAX_PROPERTIES_AREA_LENGTH = (64 * 1024 * 1024) - 8;
       // Maximum length of all the properties (including their names, values
       // and the wire protocol overhead).  Note that this value is just under
       // 64 MB.

    static const int k_MAX_PROPERTY_NAME_LENGTH  = 4095;
        // Maximum length of a property name.

    static const int k_MAX_PROPERTY_VALUE_LENGTH = 67104745;  // ~64 MB
       // Maximum length of a property value.  Note that this value is just
       // under 64 MB.  Also note that this value is calculated assuming that
       // there is only one property and property's name has maximum allowable
       // length, and also takes into consideration the protocol overhead.

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(MessageProperties,
                                   bslma::UsesBslmaAllocator)

    // CREATORS
    explicit
    MessageProperties(bslma::Allocator *basicAllocator = 0);
        // Create an empty instance of 'MessageProperties'.  Optionally
        // specify a 'basicAllocator' used to supply memory.  Note that it is
        // more efficient to use a 'MessageProperties' object retrieved via
        // 'Session::loadMessageProperties', instead of creating it via this
        // constructor.

    MessageProperties(const MessageProperties&  other,
                      bslma::Allocator         *basicAllocator = 0);
        // Create an instance of 'MessageProperties' having the same value as
        // the specified 'other', that will use the optionally specified
        // 'basicAllocator' to supply memory.

    ~MessageProperties();
        // Destroy this object.

    // MANIPULATORS
    MessageProperties& operator=(const MessageProperties& rhs);
        // Assign to this object the value of the specified 'rhs' object.

    bool remove(const bsl::string&        name,
                bmqt::PropertyType::Enum *buffer = 0);
        // Remove the property with the specified 'name' if one exists and
        // return true and load into the optionally specified 'buffer' the data
        // type of the property.  Return false if the 'name' property does not
        // exist, and leave the 'buffer' unchanged.

    void clear();
        // Remove all properties from this instance.  Note that 'numProperties'
        // will return zero after invoking this method.

    int setPropertyAsBool(const bsl::string& name, bool value);
    int setPropertyAsChar(const bsl::string& name, char value);
    int setPropertyAsShort(const bsl::string& name, short value);
    int setPropertyAsInt32(const bsl::string& name, bsl::int32_t value);
    int setPropertyAsInt64(const bsl::string& name, bsls::Types::Int64 value);
    int setPropertyAsString(const bsl::string& name, const bsl::string& value);
    int setPropertyAsBinary(const bsl::string&       name,
                            const bsl::vector<char>& value);
        // Set a property with the specified 'name' having the specified
        // 'value' with the corresponding data type.  Return zero on success,
        // and a non-zero value in case of failure.  Note that if a property
        // with 'name' and the same type exists, it will be updated with the
        // provided 'value', however if the data type of the existing property
        // differs, an error will be returned.

    int streamIn(const bdlbb::Blob& blob);
        // Populate this instance with its BlazingMQ wire protocol
        // representation from the specified 'blob'.  Return zero on success,
        // and a non-zero value otherwise.

    // ACCESSORS
    int numProperties() const;
        // Return the total number of properties set in this instance.

    int totalSize() const;
        // Return the total size (in bytes) of the wire representation of this
        // instance.  Note that returned value includes the BlazingMQ wire
        // protocol overhead as well.

    bool hasProperty(const bsl::string&        name,
                     bmqt::PropertyType::Enum *type = 0) const;
        // Return true if a property with the specified 'name' exists and load
        // into the optionally specified 'type' the type of the property.
        // Return false otherwise.

    bmqt::PropertyType::Enum propertyType(const bsl::string& name) const;
        // Return the type of property having the specified 'name'.  Behavior
        // is undefined unless 'hasProperty' returns true for the property
        // 'name'.

    bool getPropertyAsBool(const bsl::string& name) const;
    char getPropertyAsChar(const bsl::string& name) const;
    short getPropertyAsShort(const bsl::string& name) const;
    bsl::int32_t getPropertyAsInt32(const bsl::string& name) const;
    bsls::Types::Int64 getPropertyAsInt64(const bsl::string& name) const;
    const bsl::string& getPropertyAsString(const bsl::string& name) const;
    const bsl::vector<char>& getPropertyAsBinary(
                                                const bsl::string& name) const;
        // Return the property having the corresponding type and the specified
        // 'name'.  Behavior is undefined unless property with 'name' exists.

    bool getPropertyAsBoolOr(const bsl::string& name, bool value) const;
    char getPropertyAsCharOr(const bsl::string& name, char value) const;
    short getPropertyAsShortOr(const bsl::string& name, short value) const;
    bsl::int32_t getPropertyAsInt32Or(const bsl::string& name,
                                      bsl::int32_t       value) const;
    bsls::Types::Int64 getPropertyAsInt64Or(const bsl::string& name,
                                            bsls::Types::Int64 value) const;
    const bsl::string& getPropertyAsStringOr(const bsl::string& name,
                                             const bsl::string& value) const;
    const bsl::vector<char>& getPropertyAsBinaryOr(
                                         const bsl::string&       name,
                                         const bsl::vector<char>& value) const;
        // Return the property having the corresponding type and the specified
        // 'name' if property with such a name exists.  Return the specified
        // 'value' if property with 'name' does not exist.

    const bdlbb::Blob&
    streamOut(bdlbb::BlobBufferFactory *bufferFactory) const;
        // Return a blob having the BlazingMQ wire protocol representation of
        // this instance, using the specified 'bufferFactory' to build the
        // blob.  Behavior is undefined unless 'bufferFactory' is non-null.
        // Note that if this instance is empty (i.e., 'numProperties()' == 0),
        // returned blob will be empty.  In other words, an empty instance has
        // no wire representation.

    bsl::ostream& print(bsl::ostream& stream,
                        int           level          = 0,
                        int           spacesPerLevel = 4) const;
        // Format this object to the specified output 'stream' at the (absolute
        // value of) the optionally specified indentation 'level' and return a
        // reference to 'stream'.  If 'level' is specified, optionally specify
        // 'spacesPerLevel', the number of spaces per indentation level for
        // this and all of its nested objects.  If 'level' is negative,
        // suppress indentation of the first line.  If 'spacesPerLevel' is
        // negative format the entire output on one line, suppressing all but
        // the initial indentation (as governed by 'level').  If 'stream' is
        // not valid on entry, this operation has no effect.
};

// FREE OPERATORS
bsl::ostream& operator<<(bsl::ostream& stream, const MessageProperties& rhs);
    // Format the specified 'rhs' to the specified output 'stream' and return a
    // reference to the modifiable 'stream'.


                      // ===============================
                      // class MessagePropertiesIterator
                      // ===============================

class MessagePropertiesIterator {
    // Provide a mechanism to iterator over all the properties in an instance
    // of 'bmqa::MessageProperties'.  The order of the iteration is
    // implementation defined.  An iterator is *valid* if it is associated with
    // a property , otherwise it is *invalid*.  Behavior is undefined if the
    // underlying instance of 'bmqa::MessageProperties' is modified during the
    // lifetime of this iterator.

  private:
    // PRIVATE CONSTANTS
    static const int k_MAX_SIZEOF_BMQP_MESSAGEPROPERTIESITER = 64;
        // Constant representing the maximum size of a
        // 'bmqp::MessagePropertiesIterator' object, so that the below
        // AlignedBuffer is big enough.

    // PRIVATE TYPES
    typedef
    bsls::AlignedBuffer<k_MAX_SIZEOF_BMQP_MESSAGEPROPERTIESITER> ImplBuffer;

  private:
    // DATA
    mutable bmqp::MessagePropertiesIterator *d_impl_p;
                                        // Pointer to the implementation object
                                        // in 'd_buffer'.  This variable *must*
                                        // *be* the first member of this class.
                                        // If the value is null, the object
                                        // has not been constructed in the
                                        // 'd_buffer'.

    ImplBuffer                               d_buffer;
                                        // Buffer containing the implementation
                                        // object

  public:
    // CREATORS
    MessagePropertiesIterator();
        // Create an empty iterator instance.  The only valid operations that
        // can be invoked on an empty instance are copying, assignment and
        // destruction.

    explicit
    MessagePropertiesIterator(const MessageProperties *properties);
        // Create an iterator for the specified 'properties'.  Behavior is
        // undefined unless 'properties' is not null.

    MessagePropertiesIterator(const MessagePropertiesIterator& other);
        // Copy constructor from the specified 'other'.

    ~MessagePropertiesIterator();
        // Destroy this iterator.

    // MANIPULATORS
    MessagePropertiesIterator&
    operator=(const MessagePropertiesIterator& rhs);
        // Assignment operator from the specified 'rhs'.

    bool hasNext();
        // Advance this iterator to refer to the next property of the
        // associated 'MessageProperties' instance, if there is one and return
        // true, return false otherwise.  Behavior is undefined unless this
        // method is being invoked for the first time on this object or
        // previous call to 'hasNext' returned true.  Note that the order of
        // the iteration is not specified.

    // ACCESSORS
    const bsl::string& name() const;
        // Return a reference offering non-modifiable access to the name of the
        // property being pointed by the iterator.  Behavior is undefined
        // unless last call to 'hasNext' returned true.

    bmqt::PropertyType::Enum type() const;
        // Return the data type of property being pointed by the iterator.
        // Behavior is undefined unless last call to 'hasNext' returned true;

    bool getAsBool() const;
    char getAsChar() const;
    short getAsShort() const;
    bsl::int32_t getAsInt32() const;
    bsls::Types::Int64 getAsInt64() const;
    const bsl::string& getAsString() const;
    const bsl::vector<char>& getAsBinary() const;
        // Return property value having the corresponding type being currently
        // being pointed by this iterator instance.  Behavior is undefined
        // unless last call to 'hasNext' returned true.  Behavior is also
        // undefined unless property's data type matches the requested type.
};

}  // close package namespace


// ============================================================================
//                             INLINE DEFINITIONS
// ============================================================================


                          // -----------------------
                          // class MessageProperties
                          // -----------------------

inline
bsl::ostream&
bmqa::operator<<(bsl::ostream&                  stream,
                 const bmqa::MessageProperties& rhs)
{
    return rhs.print(stream, 0, -1);
}

}  // close enterprise namespace

#endif