// baljsn_encodeimplutil.h                                            -*-C++-*-
#ifndef INCLUDED_BALJSN_ENCODEIMPLUTIL
#define INCLUDED_BALJSN_ENCODEIMPLUTIL

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a utility to encode `bdlat`-compatible types as JSON.
//
//@CLASSES:
// baljsn::EncodeImplUtil: JSON encoder for `bdlat`-compliant types
//
//@SEE_ALSO: baljsn_encoder, baljsn_formatter, baljsn_jsonformatter
//
//@DESCRIPTION: This component provides a utility `struct` template,
// `baljsn::EncodeImplUtil` that provides functions that determine the value of
// `bdlat`-compatible value-semantic objects and represents those values in a
// JSON format.  The exact form of that representation is determined by
// template parameter`FORMATTER`.  In particular, the `struct` contains a
// `encode` function parameterized on `TYPE` that encodes an object into a
// specified stream.  There are two overloaded versions of this function:
//
// * one that writes to a `bsl::streambuf`
// * one that writes to an `bsl::ostream`
//
// This component can be used with types that support the `bdlat` framework
// (see the `bdlat` package for details), which is a compile-time interface for
// manipulating `struct`-like and `union`-like objects.  In particular, types
// generated by the `bas_codegen.pl` tool, and other dynamic types, can be
// encoded using this `class`.  The `encode` function can be invoked on any
// object that satisfies the requirements of a sequence, choice, or array
// object as defined in the `bdlat_sequencefunctions`, `bdlat_choicefunctions`,
// and `bdlat_arrayfunctions` components.
//
// Although the JSON format is easy to read and write and is very useful for
// debugging, it is relatively expensive to encode and decode and relatively
// bulky to transmit.  It is more efficient to use a binary encoding (such as
// BER) if the encoding format is under your control (see `balber_berencoder`).
//
// Refer to the details of the JSON encoding format supported by this encoder
// in the package documentation file (doc/baljsn.txt).
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: TBD
/// - - - - - - -

#include <balscm_version.h>

#include <baljsn_encoderoptions.h>

#include <bdlat_attributeinfo.h>
#include <bdlat_choicefunctions.h>
#include <bdlat_customizedtypefunctions.h>
#include <bdlat_enumfunctions.h>
#include <bdlat_formattingmode.h>
#include <bdlat_nullablevalueutil.h>
#include <bdlat_selectioninfo.h>
#include <bdlat_sequencefunctions.h>
#include <bdlat_typecategory.h>
#include <bdlat_valuetypefunctions.h>

#include <bdlb_print.h>

#include <bdlsb_memoutstreambuf.h>

#include <bdlde_base64encoder.h>

#include <bsls_assert.h>
#include <bsls_types.h>

#include <bsl_cstddef.h>
#include <bsl_iostream.h>
#include <bsl_ostream.h>
#include <bsl_sstream.h>
#include <bsl_streambuf.h>
#include <bsl_string.h>
#include <bsl_string_view.h>
#include <bsl_vector.h>

namespace BloombergLP {
namespace baljsn {
                       // ================================
                       // struct EncodeImplUtil<FORMATTER>
                       // ================================

/// This component-private utility `struct` provides a suite of functions that
/// encode `bdlat` types in the JSON format.
template <class FORMATTER>
struct EncodeImplUtil {

    // TYPES

    /// `ThisUtil` is a convenience alias for this utility `struct`.
    typedef EncodeImplUtil ThisUtil;

    /// `FormattingMode` is an alias to the type of an `int`-valued
    /// `bdlat_FormattingMode` bit-field.  A `FormattingMode` value is not
    /// valid unless it is equal to an enumerator of `bdlat_FormattingMode` or
    /// a valid bitwise-or of two or more such enumerators.  See
    /// `bdlat_formattingmode` for a description of the set of valid
    /// formatting-mode values.
    typedef int FormattingMode;

    // CLASS METHODS

    // Document Encoding

    /// Print the sequence of characters that designate the start of a JSON
    /// document to the specified `outputStream` according to the specified
    /// encoding `options`.  See `baljsn_encoderoptions` for a description of
    /// the effects, if any, of each option in the `options` on the start of a
    /// JSON document.
    static void openDocument(bsl::ostream          *outputStream,
                             const EncoderOptions&  options);

    /// Print the sequence of characters that designate the end of a JSON
    /// document to the specified `outputStream` according to the specified
    /// encoding `options`.  See the `baljsn_encoderoptions` for a description
    /// of the effects, if any, of each option in the `options` on the end of a
    /// JSON document.
    static void closeDocument(bsl::ostream          *outputStream,
                              const EncoderOptions&  options);

    // Value Encoding

    /// Encode the JSON representation of the specified `value` to the
    /// specified `jsonStream`.  Optionally specify `options` to configure
    /// aspects of the JSON representation of the `value`.  If `options` is
    /// not specified, the default `EncoderOptions` value is used.  Return 0
    /// on success, and a non-zero value otherwise.  The behavior is
    /// undefined unless the specified `TYPE` satisfies both the static and
    /// dynamic requirements of one `bdlat` type-category concept.  See
    /// `baljsn_encoderoptions` for a description of the effects, if any, of
    /// each option in the `options` on the JSON representation of the `value`.
    /// See the package-level documentation of `bdlat` for a description of the
    /// available type-category concepts.
    template <class TYPE>
    static int encode(bsl::ostream          *jsonStream,
                      const TYPE&            value,
                      const EncoderOptions&  options = EncoderOptions());

    /// Encode the JSON representation of the specified `value` to the
    /// specified `jsonStream`.  If this operation is not successful, load
    /// an unspecified, human-readable description of the error condition to
    /// the specified `logStream`.  Optionally specify `options` to
    /// configure aspects of the JSON representation of the `value`.  If
    /// `options` is not specified, the default `EncoderOptions` value is
    /// used.  Return 0 on success, and a non-zero value otherwise.  The
    /// behavior is undefined unless the specified `TYPE` satisfies both the
    /// static and dynamic requirements of one `bdlat` type-category
    /// concept.  See `baljsn_encoderoptions` for a description of the effects,
    /// if any, of each option in the `options` on the JSON representation of
    /// the `value`.  See the package-level documentation of `bdlat` for an
    /// introduction to the requirements of `bdlat` type-category concepts.
    template <class TYPE>
    static int encode(bsl::ostream          *logStream,
                      bsl::ostream          *jsonStream,
                      const TYPE&            value,
                      const EncoderOptions&  options = EncoderOptions());

    /// Encode the JSON representation of the specified `value` to the
    /// specified JSON `formatter`, according to the specified
    /// `formattingMode`.  If the representation contains no text, load the
    /// value `true` into `isValueEmpty` and the value `false` otherwise.
    /// If the specified `isFirstMember` option is `true`, then the
    /// representation of the value contains no leading sequence delimiter,
    /// and does contain such a delimiter if the remaining representation is
    /// non-empty otherwise.  Use the specified `options` to configure
    /// aspects of the JSON representation of the `value`.  If this
    /// operation is not successful, load an unspecified, human-readable
    /// description of the error condition to the specified `logStream`.
    /// Return 0 on success, and a non-zero value otherwise.  The behavior
    /// is undefined unless the specified `TYPE` satisfies both the static
    /// and dynamic requirements of one of the `bdlat` type-category
    /// concepts.  See `baljsn_encoderoptions` for a description of the
    /// effects, if any, of each option in the `options` on the JSON
    /// representation of the `value`.  See the package-level documentation of
    /// `bdlat` for an introduction to the requirements of `bdlat`
    /// type-category concepts.
    template <class TYPE>
    static int encode(bool                  *isValueEmpty,
                      FORMATTER             *formatter,
                      bsl::ostream          *logStream,
                      const TYPE&            value,
                      FormattingMode         formattingMode,
                      const EncoderOptions&  options,
                      bool                   isFirstMember);

    // Validation

    /// Determine if the specified `value` having the specified `bdlat`
    /// `category` satisfies the requirements for encoding using this
    /// component.  If the `value` meets the encoding requirements, return 0,
    /// otherwise load an unspecified, human-readable description of the
    /// requirements that are not satisfied by the `value` and return a
    /// non-zero value.  For values satisfying the `bdlat` `Choice`
    /// type-category concept, the value of the  `selectionId` attribute must
    /// not be undefined.  For values satisfying the requirements of other
    /// `bdlat` type-category concepts, there are no further requirements for
    /// encoding using this component.  See the package-level documentation of
    /// `bdlat` for an introduction to the requirements of `bdlat`
    /// type-category concepts.
    template <class TYPE>
    static int validate(bsl::ostream               *logStream,
                        const TYPE&                 value,
                        bdlat_TypeCategory::Choice  category);
    template <class TYPE, class CATEGORY>
    static int validate(bsl::ostream *logStream,
                        const TYPE&   value,
                        CATEGORY      category);

    /// Determine if the specified `value` satisfies the requirements for
    /// encoding using this component.  If the `value` meets the encoding
    /// requirements, return 0, otherwise load an unspecified, human-readable
    /// description of the requirements that are not satisfied by the `value`
    /// to the specified `logStream` and return a non-zero value.  The `value`
    /// is required to not have an undefined `selectionId`.
    template <class TYPE>
    static int validateChoice(bsl::ostream *logStream, const TYPE& value);

    // Encoding Values That Have Specific Type Categories

    /// Encode the JSON representation of the specified `value` to the
    /// specified JSON `formatter`.  Use the specified `options` to configure
    /// aspects of the JSON representation of the `value`.  Return 0 on
    /// success, and a non-zero value otherwise.
    static int encodeCharArray(FORMATTER                *formatter,
                               const bsl::vector<char>&  value,
                               const EncoderOptions&     options);

    /// Encode the JSON representation of the specified `value` to the
    /// specified JSON `formatter`.  Use the specified `options` to
    /// configure aspects of the JSON representation of the `value`.  Return
    /// 0 on success, and a non-zero value otherwise.  The behavior is
    /// undefined unless the specified `TYPE` satisfies both the static and
    /// dynamic requirements of the `Simple` `bdlat` type-category concept.
    /// See `baljsn_encoderoptions` for a description of the effects, if any,
    /// of each option in the `options` on the JSON representation of the
    /// `value`.  See the package-level documentation of `bdlat` for an
    /// introduction to the requirements of `bdlat` type-category concepts.
    template <class TYPE>
    static int encodeSimpleValue(FORMATTER             *formatter,
                                 const TYPE&            value,
                                 const EncoderOptions&  options);

    // Encoding Prefixes and Suffixes

    /// If the specified `formattingMode` does not have the
    /// `bdlat_FormattingMode::e_UNTAGGED` bit set, encode a "left brace"
    /// JSON token to the specified `formatter`, and encoding nothing to the
    /// `formatter` otherwise.  If this operation encodes a token to the
    /// formatter, load the value `false` to the specified `isPrefixEmpty`,
    /// and the value `true` otherwise.
    static void encodeObjectPrefix(bool           *isPrefixEmpty,
                                   FORMATTER      *formatter,
                                   FormattingMode  formattingMode);

    /// If the specified `formattingMode` does not have the
    /// `bdlat_FormattingMode::e_UNTAGGED` bit set, encode a "right brace"
    /// JSON token to the specified `formatter`, and encoding nothing to the
    /// `formatter` otherwise.  If this operation encodes a token to the
    /// `formatter`, load the value `false` to the specified
    /// `isSuffixEmpty`, and the value `true` otherwise.
    static void encodeObjectSuffix(bool           *isSuffixEmpty,
                                   FORMATTER      *formatter,
                                   FormattingMode  formattingMode);

    // Encoding Arrays That Have Specific Shapes

    /// Encode the representation of the empty-array JSON value to the
    /// specified `formatter`.
    static void encodeEmptyArray(FORMATTER *formatter);

    /// Encode the JSON representation of the specified `value` to the
    /// specified JSON `formatter`.  Use the specified `options` to configure
    /// aspects of the JSON representation of the `value`.  If this operation
    /// is not successful, load an unspecified, human-readable description of
    /// the error condition to the specified `logStream`.  Return 0 on success,
    /// and a non-zero value otherwise.  The behavior is undefined unless the
    /// `value` is non-empty and the specified `TYPE` satisfies both the static
    /// and dynamic requirements of the `Array` `bdlat` type-category concept.
    /// See `baljsn_encoderoptions` for a description of the effects, if any,
    /// of each option in the `options` on the JSON representation of the
    /// `selection`.  See the package-level documentation of `bdlat` for an
    /// introduction to the requirements of `bdlat` type-category concepts.
    template <class TYPE>
    static int encodeNonEmptyArray(FORMATTER             *formatter,
                                   bsl::ostream          *logStream,
                                   const TYPE&            value,
                                   const EncoderOptions&  options);

    // Encoding Generalized Members

    /// Encode the JSON representation of the specified object `member`
    /// having the specified `memberName` to the specified JSON `formatter`,
    /// according to the specified `formattingMode`.  If the representation
    /// contains no text, load the value `true` to `isMemberEmpty` and the
    /// value `false` otherwise.  If the specified `isFirstMember` option is
    /// `true`, then the representation of the member contains no leading
    /// sequence delimiter, and does contain such a delimiter otherwise.
    /// Use the specified `options` to configure aspects of the JSON
    /// representation of the `member`.  If this operation is not
    /// successful, load an unspecified, human-readable description of the
    /// error condition to the specified `logStream`.  Return 0 on success,
    /// and a non-zero value otherwise.  The behavior is undefined unless
    /// the specified `TYPE` satisfies both the static and dynamic
    /// requirements of the specified `category` `bdlat` type-category
    /// concept.  See `baljsn_encoderoptions` for a description of the effects,
    /// if any, of each option in the `options` on the JSON representation of
    /// the `selection`.  See the package-level documentation of `bdlat` for
    /// an introduction to the requirements of `bdlat` type-category concepts.
    static int encodeMember(bool                      *isMemberEmpty,
                            FORMATTER                 *formatter,
                            bsl::ostream              *logStream,
                            const bsl::string_view&    memberName,
                            const bsl::vector<char>&   member,
                            FormattingMode             formattingMode,
                            const EncoderOptions&      options,
                            bool                       isFirstMember,
                            bdlat_TypeCategory::Array  category);
    template <class TYPE>
    static int encodeMember(bool                      *isMemberEmpty,
                            FORMATTER                 *formatter,
                            bsl::ostream              *logStream,
                            const bsl::string_view&    memberName,
                            const TYPE&                member,
                            FormattingMode             formattingMode,
                            const EncoderOptions&      options,
                            bool                       isFirstMember,
                            bdlat_TypeCategory::Array  category);
    template <class TYPE, class OTHER_CATEGORY>
    static int encodeMember(bool                    *isMemberEmpty,
                            FORMATTER               *formatter,
                            bsl::ostream            *logStream,
                            const bsl::string_view&  memberName,
                            const TYPE&              member,
                            FormattingMode           formattingMode,
                            const EncoderOptions&    options,
                            bool                     isFirstMember,
                            OTHER_CATEGORY           category);

    /// If the specified `isFirstMember` flag is `false`, encode a "comma"
    /// JSON token to the specified `formatter`, and do not encode a "comma"
    /// JSON token otherwise.  If the specified `formattingMode` does not
    /// have the `bdlat_FormattingMode::e_UNTAGGED` bit set, encode a JSON
    /// "string" token having the specified `memberName` contents, and
    /// encode a JSON "colon" token after the string, and do not encode
    /// these tokens otherwise.  If this operation is not successful, load
    /// an unspecified, human-readable description of the error condition to
    /// the specified `logStream`.  Optionally specify `isPrefixEmpty`.  If
    /// this operation encodes a token to the formatter, load the value
    /// `false` to `isPrefixEmpty` if specified, and the value `true`
    /// otherwise.  Return 0 on success, and a non-zero value otherwise.
    static int encodeMemberPrefix(FORMATTER               *formatter,
                                  bsl::ostream            *logStream,
                                  const bsl::string_view&  memberName,
                                  bool                     isFirstMember);
    static int encodeMemberPrefix(FORMATTER               *formatter,
                                  bsl::ostream            *logStream,
                                  const bsl::string_view&  memberName,
                                  FormattingMode           formattingMode,
                                  bool                     isFirstMember);
    static int encodeMemberPrefix(bool                    *isPrefixEmpty,
                                  FORMATTER               *formatter,
                                  bsl::ostream            *logStream,
                                  const bsl::string_view&  memberName,
                                  FormattingMode           formattingMode,
                                  bool                     isFirstMember);
};

                // ================================================
                // struct EncodeImplUtil_ValueDispatcher<FORMATTER>
                // ================================================

/// This component-private class provides a function object used to encode
/// values that satisfy one of the `bdlat` type-category concepts.
///
/// This class's constructor closes over the `formatter`, `logStream`, and
/// `options`  parameters that are shared between all encoding operations
/// provided in this component.  The function-call operator of this class
/// provides an overload set that accepts an object that satisfies one of
/// the `bdlat` type-category concepts, and a `bdlat_TypeCategory` tag type
/// that corresponds to the object's `bdlat` type category.  Each
/// function-call-operator overload encodes a JSON representation of the
/// specified value to the `formatter` supplied on construction.
template <class FORMATTER>
class EncodeImplUtil_ValueDispatcher {

  public:
    // TYPES

    /// `FormattingMode` is an alias to the type of an `int`-valued
    /// `bdlat_FormattingMode` bit-field.  A `FormattingMode` value is not
    /// valid unless it is equal to an enumerator of `bdlat_FormattingMode`
    /// or a valid bitwise-or of two or more such enumerators.  See the
    /// component-level documentation of {`bdlat_formattingmode`} for a
    /// description of the set of valid formatting-mode values.
    typedef int FormattingMode;

  private:
    // DATA

    // wrapper around the output stream that determines the whitespace to
    // emit around each JSON token
    FORMATTER            *d_formatter_p;

    // human-readable descriptions of all encountered error conditions
    bsl::ostream         *d_logStream_p;

    // options set by the caller of the encoding operation that controls
    // some aspects of the token sequence to emit
    const EncoderOptions *d_options_p;

    // formatting mode of the value
    FormattingMode        d_formattingMode;

    // if `false` then emit a leading sequence delimiter before the
    // representation of the value, otherwise do not emit a leading
    // sequence delimiter
    bool                  d_isNextObjectFirst;

  public:
    // CREATORS

    /// Construct an `EncodeImplUtil_ValueDispatcher` object having the
    /// specified `formatter`, `logStream`, `formattingMode`,
    /// `isNextObjectFirst`, and `options` attributes.
    EncodeImplUtil_ValueDispatcher(FORMATTER             *formatter,
                                   bsl::ostream          *logStream,
                                   FormattingMode         formattingMode,
                                   bool                   isNextObjectFirst,
                                   const EncoderOptions&  options);

    // MANIPULATORS

    /// Encode the JSON representation of the specified `value` to the JSON
    /// `formatter` attribute of this object, according to the `formattingMode`
    /// attribute of this object.  If the representation contains no text and
    /// the `isFirstSubObject` attribute of this object is `true`, set the
    /// `isNextObjectFirst` attribute of this object to `true`, and the value
    /// `false` otherwise. If `isFirstSubObject` is `true`, then the
    /// representation of the value contains no leading sequence delimiter, and
    /// does contain such a delimiter otherwise. The `options` attribute of
    /// this object configures aspects of the JSON representation of the
    /// `value`.  If this operation is not successful, load an unspecified,
    /// human-readable description of the error condition to the `logStream`
    /// attribute of this object.  Return 0 on success, and a non-zero value
    /// otherwise.  The behavior is undefined unless the specified `TYPE`
    /// satisfies both the static and dynamic requirements of the specified
    /// `category` `bdlat` type-category concept.  See `baljsn_encoderoptions`
    /// for a description of the effects, if any, of each option in the
    /// `options` on the JSON representation of the `value`.  See the
    /// package-level documentation of `bdlat` for an introduction to the
    /// requirements of `bdlat` type-category concepts.
    int operator()(const bsl::vector<char>&  value,
                   bdlat_TypeCategory::Array category);
    template <class TYPE>
    int operator()(const TYPE& value, bdlat_TypeCategory::Array category);
    template <class TYPE>
    int operator()(const TYPE& value, bdlat_TypeCategory::Choice category);
    template <class TYPE>
    int operator()(const TYPE&                        value,
                   bdlat_TypeCategory::CustomizedType category);
    template <class TYPE>
    int operator()(const TYPE&                     value,
                   bdlat_TypeCategory::DynamicType category);
    template <class TYPE>
    int operator()(const TYPE&                     value,
                   bdlat_TypeCategory::Enumeration category);
    template <class TYPE>
    int operator()(const TYPE&                       value,
                   bdlat_TypeCategory::NullableValue category);
    template <class TYPE>
    int operator()(const TYPE& value, bdlat_TypeCategory::Sequence category);
    template <class TYPE>
    int operator()(const TYPE& value, bdlat_TypeCategory::Simple category);

    /// The behavior of this function is undefined.
    template <class TYPE>
    int operator()(const TYPE&, bslmf::Nil);

    // ACCESSORS

    /// Return the value of the `isNextObjectFirst` attribute of this
    /// object.
    bool isNextObjectFirst() const;
};

                // ==============================================
                // class EncodeImplUtil_ElementVisitor<FORMATTER>
                // ==============================================

/// This component-private class provides a function object that closes over
/// the `formatter`, `logStream`, and `options` parameters that are shared
/// between all encoding operations provided in this component.  The
/// function-call operator of this class provides an overload set that accepts
/// an "element" object that satisfies one of the `bdlat` type-category
/// concepts.
template <class FORMATTER>
class EncodeImplUtil_ElementVisitor {

    // DATA

    // wrapper around the output stream that determines the whitespace to emit
    // around each JSON token
    FORMATTER            *d_formatter_p;

    // human-readable descriptions of all encountered error conditions
    bsl::ostream         *d_logStream_p;

    // options set by the caller of the encoding operation that controls some
    // aspects of the token sequence to emit
    const EncoderOptions *d_options_p;

    // if `false` then emit a leading sequence delimiter before the
    // representation of the value, otherwise do not emit a leading sequence
    // delimiter
    bool                  d_isNextElementFirst;

  public:
    // CREATORS

    /// Construct an `EncodeImplUtil_ElementVisitor` object having the
    /// specified `formatter`, `logStream`, `isNextElementFirst`, and `options`
    /// attributes.
    EncodeImplUtil_ElementVisitor(FORMATTER             *formatter,
                                  bsl::ostream          *logStream,
                                  bool                   isNextElementFirst,
                                  const EncoderOptions&  options);

    // MANIPULATORS

    /// Encode the JSON representation of the specified `value` to the JSON
    /// `formatter` attribute of this object, according to the `formattingMode`
    /// attribute of this object.  If the `isNextElementFirst` attribute of
    /// this object is `true`, then the representation of the value contains no
    /// leading sequence delimiter, and does contain such a delimiter
    /// otherwise.  The `options` attribute of this object configures aspects
    /// of the JSON representation of the `value`.  If this operation is not
    /// successful, load an unspecified, human-readable description of the
    /// error condition to the `logStream` attribute of this object.  Return 0
    /// on success, and a non-zero value otherwise. The behavior is undefined
    /// unless the specified `TYPE` satisfies both the static and dynamic
    /// requirements of the specified `category` `bdlat` type-category concept.
    /// See the component-level documentation of {`baljsn_encoderoptions`}
    /// for a description of the effects, if any, of each option in the
    /// `options` on the JSON representation of the `value`.  See the
    /// package-level documentation of {`bdlat`} for an introduction to the
    /// requirements of `bdlat` type-category concepts.
    template <class TYPE>
    int operator()(const TYPE& element);

    // ACCESSORS

    /// Return the value of the `isNextElementFirst` attribute of this
    /// object.
    bool isNextElementFirst() const;
};

                // =================================================
                // class EncodeImplUtil_ElementDispatcher<FORMATTER>
                // =================================================

/// This component-private class provides a function object that closes over
/// the `formatter`, `logStream`, and `options` parameters that are shared
/// between all encoding operations provided in this component.  The
/// function-call operator of this class provides an overload set that accepts
/// an "element" object that satisfies one of the `bdlat` type-category
/// concepts, and an optional `bdlat_TypeCategory` tag type that corresponds to
/// the element's `bdlat` type category.  Each function-call-operator overload
/// encodes a JSON representation of the specified selection to the `formatter`
/// supplied on construction.
template <class FORMATTER>
class EncodeImplUtil_ElementDispatcher {

    // DATA

    // wrapper around the output stream that determines the whitespace to emit
    // around each JSON token
    FORMATTER            *d_formatter_p;

    // human-readable descriptions of all encountered error conditions
    bsl::ostream         *d_logStream_p;

    // options set by the caller of the encoding operation that controls some
    // aspects of the token sequence to emit
    const EncoderOptions *d_options_p;

    // if `false` then emit a leading sequence delimiter before the
    // representation of the value, otherwise do not emit a leading sequence
    // delimiter
    bool                  d_isNextElementFirst;

  public:
    // CREATORS

    /// Construct an `EncodeImplUtil_ElementDispatcher` object having the
    /// specified `formatter`, `logStream`, `isNextElementFirst`, and `options`
    /// attributes.
    EncodeImplUtil_ElementDispatcher(FORMATTER             *formatter,
                                     bsl::ostream          *logStream,
                                     bool                   isNextElementFirst,
                                     const EncoderOptions&  options);

    // MANIPULATORS

    /// Encode the JSON representation of the specified `value` to the JSON
    /// `formatter` attribute of this object, according to the specified
    /// `formattingMode`.  If the representation contains no text and the
    /// specified `isFirstElement` is `true`, load the value `true` into the
    /// specified `isNextElementFirst`, and the value `false` otherwise.  If
    /// `isFirstElement` is `true`, then the representation of the value
    /// contains no leading sequence delimiter, and does contain such a
    /// delimiter otherwise.  The `options` attribute of this object
    /// configures aspects of the JSON representation of the `value`.  If
    /// this operation is not successful, load an unspecified,
    /// human-readable description of the error condition to the `logStream`
    /// attribute of this object.  Return 0 on success, and a non-zero value
    /// otherwise.  The behavior is undefined unless the specified `TYPE`
    /// satisfies both the static and dynamic requirements of the specified
    /// `category` `bdlat` type-category concept.  See `baljsn_encoderoptions`
    /// for a description of the effects, if any, of each option in the
    /// `options` on the JSON representation of the `value`.  See the
    /// package-level documentation of `bdlat` for an introduction to the
    /// requirements of `bdlat` type-category concepts.
    int operator()(const bsl::vector<char>&  element,
                   bdlat_TypeCategory::Array category);
    template <class TYPE>
    int operator()(const TYPE& element, bdlat_TypeCategory::Array category);
    template <class TYPE>
    int operator()(const TYPE& element, bdlat_TypeCategory::Choice category);
    template <class TYPE>
    int operator()(const TYPE&                        element,
                   bdlat_TypeCategory::CustomizedType category);
    template <class TYPE>
    int operator()(const TYPE&                     element,
                   bdlat_TypeCategory::DynamicType category);
    template <class TYPE>
    int operator()(const TYPE&                     element,
                   bdlat_TypeCategory::Enumeration category);
    template <class TYPE>
    int operator()(const TYPE&                       element,
                   bdlat_TypeCategory::NullableValue category);
    template <class TYPE>
    int operator()(const TYPE& element, bdlat_TypeCategory::Sequence category);
    template <class TYPE>
    int operator()(const TYPE& element, bdlat_TypeCategory::Simple category);

    /// The behavior of this function is undefined.
    template <class TYPE>
    int operator()(const TYPE&, bslmf::Nil);

    // ACCESSORS

    /// Return the value of the `isNextElementFirst` attribute of this object.
    bool isNextElementFirst() const;
};

                // ================================================
                // class EncodeImplUtil_SelectionVisitor<FORMATTER>
                // ================================================

/// This component-private class provides a function object that closes over
/// the `formatter`, `logStream`, and `options` parameters that are shared
/// between all encoding operations provided in this component.  The
/// function-call operator of this class provides an overload set that
/// accepts a "selection" object that satisfies one of the `bdlat`
/// type-category concepts, and a "selection info" object that describes
/// various metadata of the selection.
template <class FORMATTER>
class EncodeImplUtil_SelectionVisitor {

  public:
    // TYPES

    /// `FormattingMode` is an alias to the type of an `int`-valued
    /// `bdlat_FormattingMode` bit-field.  A `FormattingMode` value is not
    /// valid unless it is equal to an enumerator of `bdlat_FormattingMode`
    /// or a valid bitwise-or of two or more such enumerators.  See the
    /// component-level documentation of {`bdlat_formattingmode`} for a
    /// description of the set of valid formatting-mode values.
    typedef int FormattingMode;

  private:
    // DATA

    // wrapper around the output stream that determines the whitespace to
    // emit around each JSON token
    FORMATTER            *d_formatter_p;

    // human-readable descriptions of all encountered error conditions
    bsl::ostream         *d_logStream_p;

    // if `false` then emit a leading sequence delimiter before the
    // representation of the value, otherwise do not emit a leading
    // sequence delimiter
    bool                  d_isNextObjectFirst;

    // options set by the caller of the encoding operation that controls
    // some aspects of the token sequence to emit
    const EncoderOptions *d_options_p;

  public:
    // CREATORS

    /// Construct an `EncodeImplUtil_SelectionVisitor` object having the
    /// specified `formatter`, `logStream`, `isNextObjectFirst`, and `options`
    /// attributes.
    EncodeImplUtil_SelectionVisitor(FORMATTER             *formatter,
                                    bsl::ostream          *logStream,
                                    bool                   isNextObjectFirst,
                                    const EncoderOptions&  options);

    // MANIPULATORS

    /// Encode the JSON representation of the specified `selection`, having
    /// the name equal to the `name` attribute of the specified
    /// `selectionInfo` to the JSON `formatter` attribute of this object,
    /// according to the specified `formattingMode`.  If the
    /// `isNextObjectFirst` attribute of this object is `true`, then the
    /// representation of the selection contains no leading sequence
    /// delimiter, and does contain such a delimiter otherwise.  The
    /// `options` attribute of this object configures aspects of the JSON
    /// representation of the `selection`.  If this operation is not
    /// successful, load an unspecified, human-readable description of the
    /// error condition to the `logStream` attribute of this object.  Return
    /// 0 on success, and a non-zero value otherwise.  The behavior is
    /// undefined unless the `selection` satisfies both the static and
    /// dynamic requirements of the `bdlat` type-category concept
    /// corresponding to the specified `category`.  See the component-level
    /// documentation of {`baljsn_encoderoptions`} for a description of the
    /// effects, if any, of each option in the `options` on the JSON
    /// representation of the `selection`.  See the package-level
    /// documentation of {`bdlat`} for an introduction to the requirements
    /// of `bdlat` type-category concepts.
    template <class TYPE, class SELECTION_INFO>
    int operator()(const TYPE& selection, const SELECTION_INFO& selectionInfo);

    // ACCESSORS

    /// Return the value of the `isNextObjectFirst` attribute of this
    /// object.
    bool isNextObjectFirst() const;
};

                // ===================================================
                // class EncodeImplUtil_SelectionDispatcher<FORMATTER>
                // ===================================================

/// This component-private class provides a function object that closes over
/// the `formatter`, `logStream`, and `options` parameters that are shared
/// between all encoding operations provided in this component.  The
/// function-call operator of this class provides an overload set that
/// accepts a "selection" object that satisfies one of the `bdlat`
/// type-category concepts, and an optional `bdlat_TypeCategory` tag type
/// that corresponds to the selections's `bdlat` type category.  Each
/// function-call-operator overload encodes a JSON representation of the
/// specified selection to the `formatter` supplied on construction.
template <class FORMATTER>
class EncodeImplUtil_SelectionDispatcher {

  public:
    // TYPES

    /// `FormattingMode` is an alias to the type of an `int`-valued
    /// `bdlat_FormattingMode` bit-field.  A `FormattingMode` value is not
    /// valid unless it is equal to an enumerator of `bdlat_FormattingMode`
    /// or a valid bitwise-or of two or more such enumerators.  See the
    /// component-level documentation of {`bdlat_formattingmode`} for a
    /// description of the set of valid formatting-mode values.
    typedef int FormattingMode;

  private:
    // DATA

    // wrapper around the output stream that determines the whitespace to
    // emit around each JSON token
    FORMATTER            *d_formatter_p;

    // human-readable descriptions of all encountered error conditions
    bsl::ostream         *d_logStream_p;

    // options set by the caller of the encoding operation that controls
    // some aspects of the token sequence to emit
    const EncoderOptions *d_options_p;

    // name of the selection
    bsl::string_view      d_selectionName;

    // formatting mode of the selection
    FormattingMode        d_formattingMode;

    // if `false` then emit a leading sequence delimiter before the
    // representation of the value, otherwise do not emit a leading
    // sequence delimiter
    bool                  d_isNextObjectFirst;

  public:
    // CREATORS

    /// Construct an `EncodeImplUtil_SelectionDispatcher` object having the
    /// specified `formatter`, `logStream`, and `options` attributes.
    EncodeImplUtil_SelectionDispatcher(
                                    FORMATTER               *formatter,
                                    bsl::ostream            *logStream,
                                    const bsl::string_view&  selectionName,
                                    FormattingMode           formattingMode,
                                    bool                     isNextObjectFirst,
                                    const EncoderOptions&    options);

    // MANIPULATORS

    /// Encode the JSON representation of the specified `selection`, having
    /// the name equal to the `name` attribute of the specified
    /// `selectionInfo` to the JSON `formatter` attribute of this object,
    /// according to the specified `formattingMode`.  If the representation
    /// contains no text and `isFirstSubObject` is `true`, load the value
    /// `true` into the specified `isNextObjectFirst`, and the value `false`
    /// otherwise.  If `isFirstSubObject` is `true`, then the representation
    /// of the selection contains no leading sequence delimiter, and does
    /// contain such a delimiter otherwise.  The `options` attribute of this
    /// object configures aspects of the JSON representation of the
    /// `selection`.  If this operation is not successful, load an
    /// unspecified, human-readable description of the error condition to
    /// the `logStream` attribute of this object.  Return 0 on success, and
    /// a non-zero value otherwise.  The behavior is undefined unless the
    /// `selection` satisfies both the static and dynamic requirements of
    /// the `bdlat` type-category concept corresponding to the specified
    /// `category`.  See the component-level documentation of
    /// {`baljsn_encoderoptions`} for a description of the effects, if any,
    /// of each option in the `options` on the JSON representation of the
    /// `selection`.  See the package-level documentation of {`bdlat`} for
    /// an introduction to the requirements of `bdlat` type-category
    /// concepts.
    template <class TYPE>
    int operator()(const TYPE&                        selection,
                   bdlat_TypeCategory::CustomizedType category);
    template <class TYPE>
    int operator()(const TYPE&                     selection,
                   bdlat_TypeCategory::DynamicType category);
    template <class TYPE, class CATEGORY>
    int operator()(const TYPE& selection, CATEGORY category);

    /// The behavior of this function is undefined.
    template <class TYPE>
    int operator()(const TYPE& , bslmf::Nil );

    // ACCESSORS

    /// Return the value of the `isNextElementFirst` attribute of this
    /// object.
    bool isNextObjectFirst() const;
};

                // ================================================
                // class EncodeImplUtil_AttributeVisitor<FORMATTER>
                // ================================================

/// This component-private class provides a function object that closes over
/// the `formatter`, `logStream`, and `options`  parameters that are shared
/// between all encoding operations provided in this component.  The
/// function-call operator of this class provides an overload set that
/// accepts an "attribute" object that satisfies one of the `bdlat`
/// type-category concepts, and an "attribute info" object that describes
/// various metadata of the attribute.
template <class FORMATTER>
class EncodeImplUtil_AttributeVisitor {

  public:
    // TYPES

    /// `FormattingMode` is an alias to the type of an `int`-valued
    /// `bdlat_FormattingMode` bit-field.  A `FormattingMode` value is not
    /// valid unless it is equal to an enumerator of `bdlat_FormattingMode`
    /// or a valid bitwise-or of two or more such enumerators.  See the
    /// component-level documentation of {`bdlat_formattingmode`} for a
    /// description of the set of valid formatting-mode values.
    typedef int FormattingMode;

  private:
    // DATA

    // wrapper around the output stream that determines the whitespace to
    // emit around each JSON token
    FORMATTER            *d_formatter_p;

    // human-readable descriptions of all encountered error conditions
    bsl::ostream         *d_logStream_p;

    // if `false` then emit a leading sequence delimiter before the
    // representation of the value, otherwise do not emit a leading
    // sequence delimiter
    bool                  d_isNextAttributeFirst;

    // options set by the caller of the encoding operation that controls
    // some aspects of the token sequence to emit
    const EncoderOptions *d_options_p;

  public:
    // CREATORS

    /// Construct an `EncodeImplUtil_AttributeVisitor` object having the
    /// specified `formatter`, `logStream`, `isNextAttributeFirst`, and
    /// `options` attributes.
    EncodeImplUtil_AttributeVisitor(FORMATTER             *formatter,
                             bsl::ostream          *logStream,
                             bool                   isNextAttributeFirst,
                             const EncoderOptions&  options);

    // MANIPULATORS

    /// Encode the JSON representation of the specified `attribute`, having
    /// the name equal to the `name` of the specified `attributeInfo`, to
    /// the JSON `formatter` attribute of this object according to the
    /// `formattingMode` attribute of this object.  If the
    /// `isNextAttributeFirst` attribute of this object is `true`, then the
    /// representation of the attribute contains no leading sequence
    /// delimiter, and does contain such a delimiter otherwise.  The
    /// `options` attribute of this object configures aspects of the JSON
    /// representation of the `attribute`.  If this operation is not
    /// successful, load an unspecified, human-readable description of the
    /// error condition to the `logStream` attribute of this object.  Return
    /// 0 on success, and a non-zero value otherwise.  The behavior is
    /// undefined unless the `attribute` satisfies both the static and
    /// dynamic requirements of the `bdlat` type-category concept
    /// corresponding to the specified `category`.  See the component-level
    /// documentation of {`baljsn_encoderoptions`} for a description of the
    /// effects, if any, of each option in the `options` on the JSON
    /// representation of the `attribute`.  See the package-level
    /// documentation of {`bdlat`} for an introduction to the requirements
    /// of `bdlat` type-category concepts.
    template <class TYPE, class ATTRIBUTE_INFO>
    int operator()(const TYPE& attribute, const ATTRIBUTE_INFO& attributeInfo);

    // ACCESSORS

    /// Return the value of the `isNextAttributeFirst` attribute of this
    /// object.
    bool isNextAttributeFirst() const;
};

                // ===================================================
                // class EncodeImplUtil_AttributeDispatcher<FORMATTER>
                // ===================================================

/// This component-private class provides a function object that closes over
/// the `formatter`, `logStream`, and `options`  parameters that are shared
/// between all encoding operations provided in this component.  The
/// function-call operator of this class provides an overload set that
/// accepts an "attribute" object that satisfies one of the `bdlat`
/// type-category concepts, and an optional `bdlat_TypeCategory` tag type
/// that corresponds to the attribute's `bdlat` type category.  Each
/// function-call-operator overload encodes a JSON representation of the
/// specified attribute to the `formatter` supplied on construction.
template <class FORMATTER>
class EncodeImplUtil_AttributeDispatcher {

  public:
    // TYPES

    /// `FormattingMode` is an alias to the type of an `int`-valued
    /// `bdlat_FormattingMode` bit-field.  A `FormattingMode` value is not
    /// valid unless it is equal to an enumerator of `bdlat_FormattingMode`
    /// or a valid bitwise-or of two or more such enumerators.  See the
    /// component-level documentation of {`bdlat_formattingmode`} for a
    /// description of the set of valid formatting-mode values.
    typedef int FormattingMode;

  private:
    // DATA

    // wrapper around the output stream that determines the whitespace to
    // emit around each JSON token
    FORMATTER            *d_formatter_p;

    // human-readable descriptions of all encountered error conditions
    bsl::ostream         *d_logStream_p;

    // options set by the caller of the encoding operation that controls
    // some aspects of the token sequence to emit
    const EncoderOptions *d_options_p;

    // name of the attribute
    bsl::string_view      d_attributeName;

    // formatting mode of the attribute
    FormattingMode        d_formattingMode;

    // if `false` then emit a leading sequence delimiter before the
    // representation of the value, otherwise do not emit a leading
    // sequence delimiter
    bool                  d_isNextAttributeFirst;

  public:
    // CREATORS

    /// Construct an `EncodeImplUtil_AttributeDispatcher` object having the
    /// specified `formatter`, `logStream`, `attributeName`, `formattingMode`,
    /// `isNextAttributeFirst`, and `options` attributes.
    EncodeImplUtil_AttributeDispatcher(
                                 FORMATTER               *formatter,
                                 bsl::ostream            *logStream,
                                 const bsl::string_view&  attributeName,
                                 FormattingMode           formattingMode,
                                 bool                     isNextAttributeFirst,
                                 const EncoderOptions&    options);

    // MANIPULATORS

    /// Encode the JSON representation of the specified `attribute`, having
    /// the name equal to the `name` of the `attributeInfo` attribute of
    /// this object, to the JSON `formatter` attribute of this object
    /// according to the `formattingMode` attribute of this object.  If the
    /// `isNextAttributeFirst` attribute of this object is `true`, then the
    /// representation of the attribute contains no leading sequence
    /// delimiter, and does contain such a delimiter otherwise.  The
    /// `options` attribute of this object configures aspects of the JSON
    /// representation of the `attribute`.  If this operation is not
    /// successful, load an unspecified, human-readable description of the
    /// error condition to the `logStream` attribute of this object.  Return
    /// 0 on success, and a non-zero value otherwise.  The behavior is
    /// undefined unless the `attribute` satisfies both the static and
    /// dynamic requirements of the `bdlat` type-category concept
    /// corresponding to the specified `category`.  See the component-level
    /// documentation of {`baljsn_encoderoptions`} for a description of the
    /// effects, if any, of each option in the `options` on the JSON
    /// representation of the `attribute`.  See the package-level
    /// documentation of {`bdlat`} for an introduction to the requirements
    /// of `bdlat` type-category concepts.
    int operator()(const bsl::vector<char>&  attribute,
                   bdlat_TypeCategory::Array category);
    template <class TYPE>
    int operator()(const TYPE& attribute, bdlat_TypeCategory::Array category);
    template <class TYPE>
    int operator()(const TYPE&                        attribute,
                   bdlat_TypeCategory::CustomizedType category);
    template <class TYPE>
    int operator()(const TYPE&                     attribute,
                   bdlat_TypeCategory::DynamicType category);
    template <class TYPE>
    int operator()(const TYPE&                       attribute,
                   bdlat_TypeCategory::NullableValue category);
    template <class TYPE, class CATEGORY>
    int operator()(const TYPE& attribute, CATEGORY category);

    /// The behavior of this function is undefined.
    template <class TYPE>
    int operator()(const TYPE&, bslmf::Nil);

    // ACCESSORS

    /// Return the value of the `isNextAttributeFirst` attribute of this
    /// object.
    bool isNextAttributeFirst() const;
};

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

                       // --------------------------------
                       // struct EncodeImplUtil<FORMATTER>
                       // --------------------------------

// CLASS METHODS

template <class FORMATTER>
int EncodeImplUtil<FORMATTER>::encodeCharArray(
                                      FORMATTER                *formatter,
                                      const bsl::vector<char>&  value,
                                      const EncoderOptions&     encoderOptions)
{
    bsl::string          base64String;
    bdlde::Base64Encoder encoder(0);
    base64String.resize(
       bdlde::Base64Encoder::encodedLength(static_cast<int>(value.size()), 0));

    // Ensure length is a multiple of 4.

    BSLS_ASSERT(0 == (base64String.length() & 0x03));

    int numOut;
    int numIn;
    int rc = encoder.convert(base64String.begin(),
                             &numOut,
                             &numIn,
                             value.begin(),
                             value.end());

    if (rc < 0) {
        return rc;                                                    // RETURN
    }

    rc = encoder.endConvert(base64String.begin() + numOut);
    if (rc < 0) {
        return rc;                                                    // RETURN
    }

    return encodeSimpleValue(formatter, base64String, encoderOptions);
}

                               // Member Encoding

template <class FORMATTER>
int EncodeImplUtil<FORMATTER>::encodeMember(
                                     bool                      *memberIsEmpty,
                                     FORMATTER                 *formatter,
                                     bsl::ostream              *logStream,
                                     const bsl::string_view&    memberName,
                                     const bsl::vector<char>&   member,
                                     FormattingMode             formattingMode,
                                     const EncoderOptions&      options,
                                     bool                       isFirstMember,
                                     bdlat_TypeCategory::Array)
{
    int rc = ThisUtil::encodeMemberPrefix(formatter,
                                          logStream,
                                          memberName,
                                          formattingMode,
                                          isFirstMember);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    rc = ThisUtil::encodeCharArray(formatter, member, options);
    if (0 != rc) {
        (*logStream) << "Unable to encode value of element "
                     << "named: '" << memberName << "'."
                     << bsl::endl;
        return rc;                                                    // RETURN
    }

    *memberIsEmpty = false;
    return 0;
}

// Document Encoding Functions

template <class FORMATTER>
void EncodeImplUtil<FORMATTER>::openDocument(
                                          bsl::ostream          *outputStream,
                                          const EncoderOptions&  options)
{
    if (baljsn::EncoderOptions::e_PRETTY == options.encodingStyle()) {
        bdlb::Print::indent(*outputStream,
                            options.initialIndentLevel(),
                            options.spacesPerLevel());
    }
}

template <class FORMATTER>
void EncodeImplUtil<FORMATTER>::closeDocument(
                                   bsl::ostream                  *outputStream,
                                   const baljsn::EncoderOptions&  options)
{
    if (baljsn::EncoderOptions::e_PRETTY == options.encodingStyle()) {
        (*outputStream) << '\n';
    }
}

// Value Encoding

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil<FORMATTER>::encode(bsl::ostream          *jsonStream,
                                      const TYPE&            value,
                                      const EncoderOptions&  options)
{
    bdlsb::MemOutStreamBuf logStreamBuf;
    bsl::ostream           logStream(&logStreamBuf);

    return encode(&logStream, jsonStream, value, options);
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil<FORMATTER>::encode(bsl::ostream          *logStream,
                                      bsl::ostream          *jsonStream,
                                      const TYPE&            value,
                                      const EncoderOptions&  options)
{
    static const FormattingMode s_MODE = bdlat_FormattingMode::e_DEFAULT;
    static const bool           s_FIRST_MEMBER_FLAG = false;

    FORMATTER formatter(
                   *jsonStream,
                   baljsn::EncoderOptions::e_PRETTY == options.encodingStyle(),
                   options.initialIndentLevel(),
                   options.spacesPerLevel(),
                   options.escapeForwardSlash());

    bool isValueEmpty = false;

    int rc = encode(&isValueEmpty,
                    &formatter,
                    logStream,
                    value,
                    s_MODE,
                    options,
                    s_FIRST_MEMBER_FLAG);

    if (0 != formatter.nestingDepth()) {
        *logStream << "Encoding failed leaving an unclosed element (rc = "
                   << rc << ")\n";
    }

    return rc;
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil<FORMATTER>::encode(bool                  *isValueEmpty,
                                      FORMATTER             *formatter,
                                      bsl::ostream          *logStream,
                                      const TYPE&            value,
                                      FormattingMode         formattingMode,
                                      const EncoderOptions&  options,
                                      bool                   isFirstMember)
{
    EncodeImplUtil_ValueDispatcher<FORMATTER> visitor(formatter,
                                                      logStream,
                                                      formattingMode,
                                                      isFirstMember,
                                                      options);

    typedef typename bdlat_TypeCategory::Select<TYPE>::Type Category;
    int rc = visitor(value, Category());
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    *isValueEmpty = visitor.isNextObjectFirst();
    return 0;
}

// Validation

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil<FORMATTER>::validate(bsl::ostream               *logStream,
                                        const TYPE&                 value,
                                        bdlat_TypeCategory::Choice  )
{
    return validateChoice(logStream, value);
}

template <class FORMATTER>
template <class TYPE, class CATEGORY>
int EncodeImplUtil<FORMATTER>::validate(bsl::ostream *,
                                        const TYPE&   ,
                                        CATEGORY      )
{
    return 0;
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil<FORMATTER>::validateChoice(bsl::ostream *logStream,
                                                      const TYPE&   value)
{
    if (bdlat_ChoiceFunctions::k_UNDEFINED_SELECTION_ID ==
        bdlat_ChoiceFunctions::selectionId(value)) {
        (*logStream) << "Undefined selection for Choice object" << bsl::endl;
        return -1;                                                    // RETURN
    }

    return 0;
}

// Encoding Values That Have Specific Type Categories

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil<FORMATTER>::encodeSimpleValue(
                                              FORMATTER             *formatter,
                                              const TYPE&            value,
                                              const EncoderOptions&  options)
{
    return formatter->putValue(value, &options);
}

// Encoding Value Prefixes and Suffixes

template <class FORMATTER>
void EncodeImplUtil<FORMATTER>::encodeObjectPrefix(
                                                bool           *isPrefixEmpty,
                                                FORMATTER      *formatter,
                                                FormattingMode  formattingMode)
{
    if (bdlat_FormattingMode::e_UNTAGGED & formattingMode) {
        *isPrefixEmpty = true;
        return;                                                       // RETURN
    }

    formatter->openObject();

    *isPrefixEmpty = false;
}

template <class FORMATTER>
void EncodeImplUtil<FORMATTER>::encodeObjectSuffix(
                                                bool           *isSuffixEmpty,
                                                FORMATTER      *formatter,
                                                FormattingMode  formattingMode)
{
    if (bdlat_FormattingMode::e_UNTAGGED & formattingMode) {
        *isSuffixEmpty = true;
        return;                                                       // RETURN
    }

    formatter->closeObject();

    *isSuffixEmpty = false;
}

// Encoding Arrays That Have Specific Shapes

template <class FORMATTER>
void EncodeImplUtil<FORMATTER>::encodeEmptyArray(FORMATTER *formatter)
{
    formatter-> openArray(true);
    formatter->closeArray(true);
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil<FORMATTER>::encodeNonEmptyArray(
                                              FORMATTER             *formatter,
                                              bsl::ostream          *logStream,
                                              const TYPE&            value,
                                              const EncoderOptions&  options)
{
    const int size = static_cast<int>(bdlat_ArrayFunctions::size(value));
    BSLS_ASSERT(0 < size);

    formatter->openArray();

    EncodeImplUtil_ElementVisitor<FORMATTER> visitor(formatter,
                                                     logStream,
                                                     true,
                                                     options);

    for (int index = 0; index != size; ++index) {
        int rc = bdlat_ArrayFunctions::accessElement(value, visitor, index);
        if (0 != rc) {
            return rc;                                                // RETURN
        }
    }

    formatter->closeArray();

    return 0;
}

// Encoding Generalized Members

/// ## Implementation Note
/// This function purposefully ignores the `EncodeEmptyArrays` option in the
/// specified `options` and always encodes the value of the specified
/// `member` array.  The caller is responsible for checking the value of
/// this option and deciding whether to obey the option or not.  Callers
/// that encode array-valued attributes of sequences must always obey the
/// option.  Callers that encode array-valued selections of choices must
/// never obey the option, and must always encode the array value.
template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil<FORMATTER>::encodeMember(
                                     bool                      *isMemberEmpty,
                                     FORMATTER                 *formatter,
                                     bsl::ostream              *logStream,
                                     const bsl::string_view&    memberName,
                                     const TYPE&                member,
                                     FormattingMode             formattingMode,
                                     const EncoderOptions&      options,
                                     bool                       isFirstMember,
                                     bdlat_TypeCategory::Array  category)
{
    int rc = ThisUtil::validate(logStream, member, category);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    rc = ThisUtil::encodeMemberPrefix(formatter,
                                      logStream,
                                      memberName,
                                      formattingMode,
                                      isFirstMember);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    if (bdlat_ArrayFunctions::size(member) == 0) {
        ThisUtil::encodeEmptyArray(formatter);

        *isMemberEmpty = false;
        return 0;                                                     // RETURN
    }

    rc = ThisUtil::encodeNonEmptyArray(formatter, logStream, member, options);
    if (0 != rc) {
        (*logStream) << "Unable to encode value of element "
                     << "named: '" << memberName << "'." << bsl::endl;
        return rc;                                                    // RETURN
    }

    *isMemberEmpty = false;
    return 0;
}

template <class FORMATTER>
template <class TYPE, class OTHER_CATEGORY>
int EncodeImplUtil<FORMATTER>::encodeMember(
                                       bool                    *isMemberEmpty,
                                       FORMATTER               *formatter,
                                       bsl::ostream            *logStream,
                                       const bsl::string_view&  memberName,
                                       const TYPE&              member,
                                       FormattingMode           formattingMode,
                                       const EncoderOptions&    options,
                                       bool                     isFirstMember,
                                       OTHER_CATEGORY           category)
{
    int rc = ThisUtil::validate(logStream, member, category);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    bool isPrefixEmpty = false;
    rc                 = ThisUtil::encodeMemberPrefix(&isPrefixEmpty,
                                                      formatter,
                                                      logStream,
                                                      memberName,
                                                      formattingMode,
                                                      isFirstMember);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    bool isValueEmpty = false;
    rc                = ThisUtil::encode(&isValueEmpty,
                                         formatter,
                                         logStream,
                                         member,
                                         formattingMode,
                                         options,
                                         !isPrefixEmpty || isFirstMember);
    if (0 != rc) {
        (*logStream) << "Unable to encode value of element "
                     << "named: '" << memberName << "'." << bsl::endl;
        return rc;                                                    // RETURN
    }

    BSLS_ASSERT(!isValueEmpty || isPrefixEmpty);
    // If the value is empty then the prefix is empty.  Otherwise, this
    // function would produce invalid JSON because it would emit a member name
    // token and a colon token, but no member value.

    *isMemberEmpty = isFirstMember && isValueEmpty;
    return 0;
}

template <class FORMATTER>
int EncodeImplUtil<FORMATTER>::encodeMemberPrefix(
                                        FORMATTER               *formatter,
                                        bsl::ostream            *logStream,
                                        const bsl::string_view&  memberName,
                                        bool                     isFirstMember)
{
    if (!isFirstMember) {
        formatter->closeMember();
    }

    int rc = formatter->openMember(memberName);
    if (0 != rc) {
        (*logStream) << "Unable to encode element name: '" << memberName
                     << "'." << bsl::endl;
        return rc;                                                    // RETURN
    }

    return 0;
}

template <class FORMATTER>
int EncodeImplUtil<FORMATTER>::encodeMemberPrefix(
                                       FORMATTER               *formatter,
                                       bsl::ostream            *logStream,
                                       const bsl::string_view&  memberName,
                                       FormattingMode           formattingMode,
                                       bool                     isFirstMember)
{
    if (bdlat_FormattingMode::e_UNTAGGED & formattingMode) {
        return 0;                                                     // RETURN
    }

    return ThisUtil::encodeMemberPrefix(formatter,
                                        logStream,
                                        memberName,
                                        isFirstMember);
}

template <class FORMATTER>
int EncodeImplUtil<FORMATTER>::encodeMemberPrefix(
                                       bool                    *isPrefixEmpty,
                                       FORMATTER               *formatter,
                                       bsl::ostream            *logStream,
                                       const bsl::string_view&  memberName,
                                       FormattingMode           formattingMode,
                                       bool                     isFirstMember)
{
    if (bdlat_FormattingMode::e_UNTAGGED & formattingMode) {
        *isPrefixEmpty = true;
        return 0;                                                     // RETURN
    }

    int rc = ThisUtil::encodeMemberPrefix(formatter,
                                          logStream,
                                          memberName,
                                          isFirstMember);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    *isPrefixEmpty = false;
    return 0;
}

                // ------------------------------------------------
                // struct EncodeImplUtil_ValueDispatcher<FORMATTER>
                // ------------------------------------------------

// CREATORS
template <class FORMATTER>
inline
EncodeImplUtil_ValueDispatcher<FORMATTER>::EncodeImplUtil_ValueDispatcher(
                                      FORMATTER             *formatter,
                                      bsl::ostream          *logStream,
                                      FormattingMode         formattingMode,
                                      bool                   isNextObjectFirst,
                                      const EncoderOptions&  options)
: d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_options_p(&options)
, d_formattingMode(formattingMode)
, d_isNextObjectFirst(isNextObjectFirst)
{
}

// ACCESSORS
template <class FORMATTER>
inline
int EncodeImplUtil_ValueDispatcher<FORMATTER>::operator()(
                                               const bsl::vector<char>&  value,
                                               bdlat_TypeCategory::Array )
{
    d_isNextObjectFirst = false;
    return EncodeImplUtil<FORMATTER>::encodeCharArray(d_formatter_p,
                                                      value,
                                                      *d_options_p);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_ValueDispatcher<FORMATTER>::operator()(
                                               const TYPE&               value,
                                               bdlat_TypeCategory::Array )
{
    const bool arrayIsEmpty = (0 == bdlat_ArrayFunctions::size(value));

    if (arrayIsEmpty && !d_options_p->encodeEmptyArrays()) {
        d_isNextObjectFirst = true;
        return 0;                                                     // RETURN
    }

    if (arrayIsEmpty && d_options_p->encodeEmptyArrays()) {
        EncodeImplUtil<FORMATTER>::encodeEmptyArray(d_formatter_p);
        d_isNextObjectFirst = false;
        return 0;                                                     // RETURN
    }

    int rc = EncodeImplUtil<FORMATTER>::encodeNonEmptyArray(d_formatter_p,
                                                            d_logStream_p,
                                                            value,
                                                            *d_options_p);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_isNextObjectFirst = false;
    return 0;
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_ValueDispatcher<FORMATTER>::operator()(
                                              const TYPE&                value,
                                              bdlat_TypeCategory::Choice )
{
    int rc = EncodeImplUtil<FORMATTER>::validateChoice(d_logStream_p,
                                                               value);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    bool isPrefixEmpty = false;
    EncodeImplUtil<FORMATTER>::encodeObjectPrefix(&isPrefixEmpty,
                                                  d_formatter_p,
                                                  d_formattingMode);

    EncodeImplUtil_SelectionVisitor<FORMATTER> visitor(
                                         d_formatter_p,
                                         d_logStream_p,
                                         !isPrefixEmpty || d_isNextObjectFirst,
                                         *d_options_p);
    rc = bdlat_ChoiceFunctions::accessSelection(value, visitor);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    const bool isSelectionEmpty = visitor.isNextObjectFirst();

    bool isSuffixEmpty = false;
    EncodeImplUtil<FORMATTER>::encodeObjectSuffix(&isSuffixEmpty,
                                                  d_formatter_p,
                                                  d_formattingMode);

    d_isNextObjectFirst = isPrefixEmpty && isSelectionEmpty && isSuffixEmpty;
    return 0;
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_ValueDispatcher<FORMATTER>::operator()(
                                      const TYPE&                        value,
                                      bdlat_TypeCategory::CustomizedType )
{
    return EncodeImplUtil<FORMATTER>::encode(
                       &d_isNextObjectFirst,
                       d_formatter_p,
                       d_logStream_p,
                       bdlat_CustomizedTypeFunctions::convertToBaseType(value),
                       d_formattingMode,
                       *d_options_p,
                       d_isNextObjectFirst);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_ValueDispatcher<FORMATTER>::operator()(
                                         const TYPE&                     value,
                                         bdlat_TypeCategory::DynamicType )
{
    return bdlat_TypeCategoryUtil::accessByCategory(value, *this);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_ValueDispatcher<FORMATTER>::operator()(
                                         const TYPE&                     value,
                                         bdlat_TypeCategory::Enumeration )
{
    bsl::string valueString;
    bdlat_EnumFunctions::toString(&valueString, value);

    d_isNextObjectFirst = false;
    return EncodeImplUtil<FORMATTER>::encodeSimpleValue(d_formatter_p,
                                                        valueString,
                                                        *d_options_p);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_ValueDispatcher<FORMATTER>::operator()(
                                       const TYPE&                       value,
                                       bdlat_TypeCategory::NullableValue )
{
    if (bdlat_NullableValueFunctions::isNull(value)) {
        int rc = d_formatter_p->putNullValue();
        d_isNextObjectFirst = false;
        return rc;                                                    // RETURN
    }

    EncodeImplUtil_ValueDispatcher visitor(d_formatter_p,
                                           d_logStream_p,
                                           d_formattingMode,
                                           d_isNextObjectFirst,
                                           *d_options_p);

    int rc = bdlat::NullableValueUtil::accessValueByCategory(value, visitor);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_isNextObjectFirst = visitor.isNextObjectFirst();
    return 0;
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_ValueDispatcher<FORMATTER>::operator()(
                                            const TYPE&                  value,
                                            bdlat_TypeCategory::Sequence )
{
    bool isPrefixEmpty = false;
    EncodeImplUtil<FORMATTER>::encodeObjectPrefix(&isPrefixEmpty,
                                                  d_formatter_p,
                                                  d_formattingMode);

    EncodeImplUtil_AttributeVisitor<FORMATTER> visitor(
                                         d_formatter_p,
                                         d_logStream_p,
                                         !isPrefixEmpty || d_isNextObjectFirst,
                                         *d_options_p);

    int rc = bdlat_SequenceFunctions::accessAttributes(value, visitor);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    const bool isAttributeEmpty = visitor.isNextAttributeFirst();

    bool isSuffixEmpty = false;
    EncodeImplUtil<FORMATTER>::encodeObjectSuffix(&isSuffixEmpty,
                                                  d_formatter_p,
                                                  d_formattingMode);

    d_isNextObjectFirst = isPrefixEmpty && isAttributeEmpty && isSuffixEmpty;
    return 0;
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_ValueDispatcher<FORMATTER>::operator()(
                                             const TYPE&                value,
                                             bdlat_TypeCategory::Simple )
{
    d_isNextObjectFirst = false;
    return EncodeImplUtil<FORMATTER>::encodeSimpleValue(d_formatter_p,
                                                        value,
                                                        *d_options_p);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_ValueDispatcher<FORMATTER>::operator()(const TYPE& ,
                                                          bslmf::Nil  )
{
    BSLS_ASSERT_OPT(0 == "Unreachable");
    return -1;
}

// ACCESSORS
template <class FORMATTER>
inline
bool EncodeImplUtil_ValueDispatcher<FORMATTER>::isNextObjectFirst() const
{
    return d_isNextObjectFirst;
}

                // ----------------------------------------------
                // class EncodeImplUtil_ElementVisitor<FORMATTER>
                // ----------------------------------------------

// CREATORS
template <class FORMATTER>
inline
EncodeImplUtil_ElementVisitor<FORMATTER>::EncodeImplUtil_ElementVisitor(
                                     FORMATTER             *formatter,
                                     bsl::ostream          *logStream,
                                     bool                   isNextElementFirst,
                                     const EncoderOptions&  options)
: d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_options_p(&options)
, d_isNextElementFirst(isNextElementFirst)
{
}

// MANIPULATORS
template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_ElementVisitor<FORMATTER>::operator()(const TYPE& element)
{
    EncodeImplUtil_ElementDispatcher<FORMATTER> dispatcher(
                                                          d_formatter_p,
                                                          d_logStream_p,
                                                          d_isNextElementFirst,
                                                          *d_options_p);

    typedef typename bdlat_TypeCategory::Select<TYPE>::Type Category;
    int rc = dispatcher(element, Category());
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_isNextElementFirst = dispatcher.isNextElementFirst();
    return 0;
}

// ACCESSORS
template <class FORMATTER>
inline
bool EncodeImplUtil_ElementVisitor<FORMATTER>::isNextElementFirst() const
{
    return d_isNextElementFirst;
}

                // -------------------------------------------------
                // class EncodeImplUtil_ElementDispatcher<FORMATTER>
                // -------------------------------------------------

// CREATORS
template <class FORMATTER>
inline
EncodeImplUtil_ElementDispatcher<FORMATTER>::EncodeImplUtil_ElementDispatcher(
                                     FORMATTER             *formatter,
                                     bsl::ostream          *logStream,
                                     bool                   isNextElementFirst,
                                     const EncoderOptions&  options)
: d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_options_p(&options)
, d_isNextElementFirst(isNextElementFirst)
{
}

// MANIPULATORS
template <class FORMATTER>
inline
int EncodeImplUtil_ElementDispatcher<FORMATTER>::operator()(
                                             const bsl::vector<char>&  element,
                                             bdlat_TypeCategory::Array )
{
    if (!d_isNextElementFirst) {
        d_formatter_p->addArrayElementSeparator();
    }

    int rc = EncodeImplUtil<FORMATTER>::encodeCharArray(d_formatter_p,
                                                        element,
                                                        *d_options_p);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_isNextElementFirst = false;
    return 0;
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil_ElementDispatcher<FORMATTER>::operator()(
                                             const TYPE&               element,
                                             bdlat_TypeCategory::Array )
{
    const bool arrayIsEmpty = (0 == bdlat_ArrayFunctions::size(element));

    if (arrayIsEmpty && !d_options_p->encodeEmptyArrays()) {
        return 0;                                                     // RETURN
    }

    if (!d_isNextElementFirst) {
        d_formatter_p->addArrayElementSeparator();
    }

    if (arrayIsEmpty && d_options_p->encodeEmptyArrays()) {
        EncodeImplUtil<FORMATTER>::encodeEmptyArray(d_formatter_p);
        d_isNextElementFirst = false;
        return 0;                                                     // RETURN
    }

    int rc = EncodeImplUtil<FORMATTER>::encodeNonEmptyArray(d_formatter_p,
                                                            d_logStream_p,
                                                            element,
                                                            *d_options_p);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_isNextElementFirst = false;
    return 0;
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil_ElementDispatcher<FORMATTER>::operator()(
                                            const TYPE&                element,
                                            bdlat_TypeCategory::Choice )
{
    int rc = EncodeImplUtil<FORMATTER>::validateChoice(d_logStream_p, element);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    if (!d_isNextElementFirst) {
        d_formatter_p->addArrayElementSeparator();
    }

    d_formatter_p->openObject();

    EncodeImplUtil_SelectionVisitor<FORMATTER> visitor(d_formatter_p,
                                                       d_logStream_p,
                                                       true,
                                                       *d_options_p);
    rc = bdlat_ChoiceFunctions::accessSelection(element, visitor);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_formatter_p->closeObject();

    d_isNextElementFirst = false;
    return 0;
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil_ElementDispatcher<FORMATTER>::operator()(
                                    const TYPE&                        element,
                                    bdlat_TypeCategory::CustomizedType )
{
    return bdlat_TypeCategoryUtil::accessByCategory(
                     bdlat_CustomizedTypeFunctions::convertToBaseType(element),
                     *this);
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil_ElementDispatcher<FORMATTER>::operator()(
                                      const TYPE&                     element,
                                      bdlat_TypeCategory::DynamicType )
{
    return bdlat_TypeCategoryUtil::accessByCategory(element, *this);
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil_ElementDispatcher<FORMATTER>::operator()(
                                       const TYPE&                     element,
                                       bdlat_TypeCategory::Enumeration )
{
    if (!d_isNextElementFirst) {
        d_formatter_p->addArrayElementSeparator();
    }

    bsl::string valueString;
    bdlat_EnumFunctions::toString(&valueString, element);

    d_isNextElementFirst = false;
    return EncodeImplUtil<FORMATTER>::encodeSimpleValue(d_formatter_p,
                                                        valueString,
                                                        *d_options_p);
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil_ElementDispatcher<FORMATTER>::operator()(
                                     const TYPE&                       element,
                                     bdlat_TypeCategory::NullableValue )
{
    const bool elementIsNull = bdlat_NullableValueFunctions::isNull(element);

    if (elementIsNull) {
        if (!d_isNextElementFirst) {
            d_formatter_p->addArrayElementSeparator();
        }

        d_isNextElementFirst = false;
        int rc = d_formatter_p->putNullValue();
        return rc;                                                    // RETURN
    }

    EncodeImplUtil_ElementVisitor<FORMATTER> visitor(d_formatter_p,
                                                     d_logStream_p,
                                                     d_isNextElementFirst,
                                                     *d_options_p);

    int rc = bdlat_NullableValueFunctions::accessValue(element, visitor);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_isNextElementFirst = visitor.isNextElementFirst();
    return 0;
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil_ElementDispatcher<FORMATTER>::operator()(
                                          const TYPE&                  element,
                                          bdlat_TypeCategory::Sequence )
{
    if (!d_isNextElementFirst) {
        d_formatter_p->addArrayElementSeparator();
    }

    d_formatter_p->openObject();

    EncodeImplUtil_AttributeVisitor<FORMATTER> visitor(d_formatter_p,
                                                       d_logStream_p,
                                                       true,
                                                       *d_options_p);

    int rc = bdlat_SequenceFunctions::accessAttributes(element, visitor);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_formatter_p->closeObject();

    d_isNextElementFirst = false;
    return 0;
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil_ElementDispatcher<FORMATTER>::operator()(
                                            const TYPE&                element,
                                            bdlat_TypeCategory::Simple )
{
    if (!d_isNextElementFirst) {
        d_formatter_p->addArrayElementSeparator();
    }

    d_isNextElementFirst = false;
    return EncodeImplUtil<FORMATTER>::encodeSimpleValue(d_formatter_p,
                                                        element,
                                                        *d_options_p);
}

template <class FORMATTER>
template <class TYPE>
int EncodeImplUtil_ElementDispatcher<FORMATTER>::operator()(const TYPE& ,
                                                            bslmf::Nil  )
{
    BSLS_ASSERT_OPT(0 == "Unreachable");
    return -1;
}

// ACCESSORS
template <class FORMATTER>
inline
bool EncodeImplUtil_ElementDispatcher<FORMATTER>::isNextElementFirst() const
{
    return d_isNextElementFirst;
}

                // ------------------------------------------------
                // class EncodeImplUtil_SelectionVisitor<FORMATTER>
                // ------------------------------------------------

// CREATORS
template <class FORMATTER>
inline
EncodeImplUtil_SelectionVisitor<FORMATTER>::EncodeImplUtil_SelectionVisitor(
                                      FORMATTER             *formatter,
                                      bsl::ostream          *logStream,
                                      bool                   isNextObjectFirst,
                                      const EncoderOptions&  options)
: d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_isNextObjectFirst(isNextObjectFirst)
, d_options_p(&options)
{
}

// MANIPULATORS
template <class FORMATTER>
template <class TYPE, class SELECTION_INFO>
inline
int EncodeImplUtil_SelectionVisitor<FORMATTER>::operator()(
                                           const TYPE&           selection,
                                           const SELECTION_INFO& selectionInfo)
{
    EncodeImplUtil_SelectionDispatcher<FORMATTER> dispatcher(
                                                d_formatter_p,
                                                d_logStream_p,
                                                selectionInfo.name(),
                                                selectionInfo.formattingMode(),
                                                d_isNextObjectFirst,
                                                *d_options_p);

    typedef typename bdlat_TypeCategory::Select<TYPE>::Type Category;
    int rc = dispatcher(selection, Category());
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_isNextObjectFirst = dispatcher.isNextObjectFirst();

    return 0;
}

// ACCESSORS
template <class FORMATTER>
inline
bool EncodeImplUtil_SelectionVisitor<FORMATTER>::isNextObjectFirst() const
{
    return d_isNextObjectFirst;
}

                // ---------------------------------------------------
                // class EncodeImplUtil_SelectionDispatcher<FORMATTER>
                // ---------------------------------------------------

// CREATORS
template <class FORMATTER>
inline
EncodeImplUtil_SelectionDispatcher<FORMATTER>::
EncodeImplUtil_SelectionDispatcher(FORMATTER               *formatter,
                                   bsl::ostream            *logStream,
                                   const bsl::string_view&  selectionName,
                                   FormattingMode           formattingMode,
                                   bool                     isNextObjectFirst,
                                   const EncoderOptions&    options)
: d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_options_p(&options)
, d_selectionName(selectionName)
, d_formattingMode(formattingMode)
, d_isNextObjectFirst(isNextObjectFirst)
{
}

// MANIPULATORS
template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_SelectionDispatcher<FORMATTER>::operator()(
                                  const TYPE&                        selection,
                                  bdlat_TypeCategory::CustomizedType )
{
    return bdlat_TypeCategoryUtil::accessByCategory(
                   bdlat_CustomizedTypeFunctions::convertToBaseType(selection),
                   *this);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_SelectionDispatcher<FORMATTER>::operator()(
                                     const TYPE&                     selection,
                                     bdlat_TypeCategory::DynamicType )
{
    return bdlat_TypeCategoryUtil::accessByCategory(selection, *this);
}

template <class FORMATTER>
template <class TYPE, class CATEGORY>
inline
int EncodeImplUtil_SelectionDispatcher<FORMATTER>::operator()(
                                                         const TYPE& selection,
                                                         CATEGORY    category)
{
    return EncodeImplUtil<FORMATTER>::encodeMember(&d_isNextObjectFirst,
                                                   d_formatter_p,
                                                   d_logStream_p,
                                                   d_selectionName.data(),
                                                   selection,
                                                   d_formattingMode,
                                                   *d_options_p,
                                                   d_isNextObjectFirst,
                                                   category);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_SelectionDispatcher<FORMATTER>::operator()(const TYPE& ,
                                                              bslmf::Nil  )
{
    BSLS_ASSERT_OPT(0 == "Unreachable");
    return -1;
}

// ACCESSORS
template <class FORMATTER>
inline
bool EncodeImplUtil_SelectionDispatcher<FORMATTER>::isNextObjectFirst() const
{
    return d_isNextObjectFirst;
}

                // ------------------------------------------------
                // class EncodeImplUtil_AttributeVisitor<FORMATTER>
                // -----------------------------------------------

// CREATORS
template <class FORMATTER>
inline
EncodeImplUtil_AttributeVisitor<FORMATTER>::EncodeImplUtil_AttributeVisitor(
                                   FORMATTER             *formatter,
                                   bsl::ostream          *logStream,
                                   bool                   isNextAttributeFirst,
                                   const EncoderOptions&  options)
: d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_isNextAttributeFirst(isNextAttributeFirst)
, d_options_p(&options)
{
}

// MANIPULATORS
template <class FORMATTER>
template <class TYPE, class ATTRIBUTE_INFO>
int EncodeImplUtil_AttributeVisitor<FORMATTER>::operator()(
                                           const TYPE&           attribute,
                                           const ATTRIBUTE_INFO& attributeInfo)
{
    EncodeImplUtil_AttributeDispatcher<FORMATTER> dispatcher(
                                                d_formatter_p,
                                                d_logStream_p,
                                                attributeInfo.name(),
                                                attributeInfo.formattingMode(),
                                                d_isNextAttributeFirst,
                                                *d_options_p);

    typedef typename bdlat_TypeCategory::Select<TYPE>::Type Category;
    int rc = dispatcher(attribute, Category());
    if (0 != rc) {
        return -rc;                                                   // RETURN
    }

    d_isNextAttributeFirst = dispatcher.isNextAttributeFirst();
    return 0;
}

// ACCESSORS
template <class FORMATTER>
inline
bool EncodeImplUtil_AttributeVisitor<FORMATTER>::isNextAttributeFirst() const
{
    return d_isNextAttributeFirst;
}

                // ---------------------------------------------------
                // class EncodeImplUtil_AttributeDispatcher<FORMATTER>
                // ---------------------------------------------------

// CREATORS
template <class FORMATTER>
inline
EncodeImplUtil_AttributeDispatcher<FORMATTER>::
EncodeImplUtil_AttributeDispatcher(
                                 FORMATTER               *formatter,
                                 bsl::ostream            *logStream,
                                 const bsl::string_view&  attributeName,
                                 FormattingMode           formattingMode,
                                 bool                     isNextAttributeFirst,
                                 const EncoderOptions&    options)
: d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_options_p(&options)
, d_attributeName(attributeName)
, d_formattingMode(formattingMode)
, d_isNextAttributeFirst(isNextAttributeFirst)
{
}

// MANIPULATORS
template <class FORMATTER>
inline
int EncodeImplUtil_AttributeDispatcher<FORMATTER>::operator()(
                                           const bsl::vector<char>&  attribute,
                                           bdlat_TypeCategory::Array category)
{
    return EncodeImplUtil<FORMATTER>::encodeMember(&d_isNextAttributeFirst,
                                                   d_formatter_p,
                                                   d_logStream_p,
                                                   d_attributeName.data(),
                                                   attribute,
                                                   d_formattingMode,
                                                   *d_options_p,
                                                   d_isNextAttributeFirst,
                                                   category);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_AttributeDispatcher<FORMATTER>::operator()(
                                           const TYPE&               attribute,
                                           bdlat_TypeCategory::Array category)
{
    const bool isArrayEmpty = (0 == bdlat_ArrayFunctions::size(attribute));

    if (!d_options_p->encodeEmptyArrays() && isArrayEmpty) {
        return 0;                                                     // RETURN
    }

    return EncodeImplUtil<FORMATTER>::encodeMember(&d_isNextAttributeFirst,
                                                   d_formatter_p,
                                                   d_logStream_p,
                                                   d_attributeName.data(),
                                                   attribute,
                                                   d_formattingMode,
                                                   *d_options_p,
                                                   d_isNextAttributeFirst,
                                                   category);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_AttributeDispatcher<FORMATTER>::operator()(
                                  const TYPE&                        attribute,
                                  bdlat_TypeCategory::CustomizedType )
{
    return bdlat_TypeCategoryUtil::accessByCategory(
                   bdlat_CustomizedTypeFunctions::convertToBaseType(attribute),
                   *this);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_AttributeDispatcher<FORMATTER>::operator()(
                                     const TYPE&                     attribute,
                                     bdlat_TypeCategory::DynamicType )
{
    return bdlat_TypeCategoryUtil::accessByCategory(attribute, *this);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_AttributeDispatcher<FORMATTER>::operator()(
                                   const TYPE&                       attribute,
                                   bdlat_TypeCategory::NullableValue category)
{
    if (bdlat_NullableValueFunctions::isNull(attribute) &&
        !d_options_p->encodeNullElements()) {
        return 0;                                                     // RETURN
    }

    return EncodeImplUtil<FORMATTER>::encodeMember(&d_isNextAttributeFirst,
                                                   d_formatter_p,
                                                   d_logStream_p,
                                                   d_attributeName.data(),
                                                   attribute,
                                                   d_formattingMode,
                                                   *d_options_p,
                                                   d_isNextAttributeFirst,
                                                   category);
}

template <class FORMATTER>
template <class TYPE, class CATEGORY>
inline
int EncodeImplUtil_AttributeDispatcher<FORMATTER>::operator()(
                                                         const TYPE& attribute,
                                                         CATEGORY    category )
{
    return EncodeImplUtil<FORMATTER>::encodeMember(&d_isNextAttributeFirst,
                                                   d_formatter_p,
                                                   d_logStream_p,
                                                   d_attributeName.data(),
                                                   attribute,
                                                   d_formattingMode,
                                                   *d_options_p,
                                                   d_isNextAttributeFirst,
                                                   category);
}

template <class FORMATTER>
template <class TYPE>
inline
int EncodeImplUtil_AttributeDispatcher<FORMATTER>::operator()(const TYPE& ,
                                                              bslmf::Nil  )
{
    BSLS_ASSERT_OPT(0 == "Unreachable");
    return -1;
}

// ACCESSORS
template <class FORMATTER>
inline
bool EncodeImplUtil_AttributeDispatcher<FORMATTER>::isNextAttributeFirst()
                                                                          const
{
    return d_isNextAttributeFirst;
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2025 Bloomberg Finance L.P.
//
// 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.
// ----------------------------- END-OF-FILE ----------------------------------
