// baljsn_encoder.h                                                   -*-C++-*-
#ifndef INCLUDED_BALJSN_ENCODER
#define INCLUDED_BALJSN_ENCODER

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

//@PURPOSE: Provide a JSON encoder for 'bdlat'-compatible types.
//
//@CLASSES:
// baljsn::Encoder: JSON decoder for 'bdlat'-compliant types
//
//@SEE_ALSO: baljsn_decoder, baljsn_printutil
//
//@DESCRIPTION: This component provides a class, 'baljsn::Encoder', for
// encoding value-semantic objects in the JSON format.  In particular, the
// 'class' contains a parameterized 'encode' function that encodes an object
// into a specified stream.  There are two overloaded versions of this
// function:
//
//: o one that writes to a 'bsl::streambuf'
//: o 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: Encoding a 'bas_codegen.pl'-Generated Object into JSON
///-----------------------------------------------------------------
// Consider that we want to exchange an employee's information between two
// processes.  To allow this information exchange we will define the XML schema
// representation for that class, use 'bas_codegen.pl' to create the 'Employee'
// 'class' for storing that information, populate an 'Employee' object, and
// encode that object using the 'baljsn' encoder.
//
// First, we will define the XML schema inside a file called 'employee.xsd':
//..
//  <?xml version='1.0' encoding='UTF-8'?>
//  <xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'
//             xmlns:test='http://bloomberg.com/schemas/test'
//             targetNamespace='http://bloomberg.com/schemas/test'
//             elementFormDefault='unqualified'>
//
//      <xs:complexType name='Address'>
//          <xs:sequence>
//              <xs:element name='street' type='xs:string'/>
//              <xs:element name='city'   type='xs:string'/>
//              <xs:element name='state'  type='xs:string'/>
//          </xs:sequence>
//      </xs:complexType>
//
//      <xs:complexType name='Employee'>
//          <xs:sequence>
//              <xs:element name='name'        type='xs:string'/>
//              <xs:element name='homeAddress' type='test:Address'/>
//              <xs:element name='age'         type='xs:int'/>
//          </xs:sequence>
//      </xs:complexType>
//
//      <xs:element name='Employee' type='test:Employee'/>
//
//  </xs:schema>
//..
// Then, we will use the 'bas_codegen.pl' tool, to generate the C++ classes for
// this schema.  The following command will generate the header and
// implementation files for the all the classes in the 'test_messages'
// components in the current directory:
//..
//  $ bas_codegen.pl -m msg -p test xsdfile.xsd
//..
// Next, we will populate a 'test::Employee' object:
//..
//  test::Employee employee;
//  employee.name()                 = "Bob";
//  employee.homeAddress().street() = "Lexington Ave";
//  employee.homeAddress().city()   = "New York City";
//  employee.homeAddress().state()  = "New York";
//  employee.age()                  = 21;
//..
// Then, we will create a 'baljsn::Encoder' object:
//..
//  baljsn::Encoder encoder;
//..
// Now, we will output this object in the JSON format by invoking the 'encode'
// method of the encoder.  We will also create a 'baljsn::EncoderOptions'
// object that allows us to specify that the encoding should be done in a
// pretty format, and what the initial indent level and spaces per level should
// be.  We will then pass that object to the 'encode' method:
//..
//  bsl::ostringstream os;
//
//  baljsn::EncoderOptions options;
//  options.setEncodingStyle(baljsn::EncoderOptions::e_PRETTY);
//  options.setInitialIndentLevel(1);
//  options.setSpacesPerLevel(4);
//
//  const int rc = encoder.encode(os, employee, options);
//  assert(!rc);
//  assert(os);
//..
// Finally, we will verify that the output is as expected:
//..
//  const char EXP_OUTPUT[] = "    {\n"
//                            "        \"name\" : \"Bob\",\n"
//                            "        \"homeAddress\" : {\n"
//                            "            \"street\" : \"Lexington Ave\",\n"
//                            "            \"city\" : \"New York City\",\n"
//                            "            \"state\" : \"New York\"\n"
//                            "        },\n"
//                            "        \"age\" : 21\n"
//                            "    }\n";
//
//  assert(EXP_OUTPUT == os.str());
//..

#include <balscm_version.h>

#include <baljsn_encoderoptions.h>
#include <baljsn_formatter.h>
#include <baljsn_printutil.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 <bsla_maybeunused.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 {

                               // =============
                               // class Encoder
                               // =============

class Encoder {
    // This class provides a mechanism for encoding value-semantic objects in
    // the JSON format.  The 'encode' methods are function templates that will
    // encode any object that meets the requirements of a sequence, choice, or
    // array object as defined in the 'bdlat_sequencefunctions',
    // 'bdlat_choicefunctions', and 'bdlat_choicefunctions' components
    // respectively.  These generic frameworks provide a common compile-time
    // interface for accessing struct-like and union-like objects.  In
    // particular, the types generated by 'bas_codegen.pl' provide the
    // necessary interface and can be encoded using this component.

    // DATA
    bsl::ostringstream d_logStream;  // stream used for logging

    // NOT IMPLEMENTED
    Encoder(const Encoder&);

    // PRIVATE MANIPULATORS
    bsl::ostream& logStream();
        // Return the stream for logging.

  public:
    // CREATORS
    explicit Encoder(bslma::Allocator *basicAllocator = 0);
        // Create a encoder object.  Optionally specify a 'basicAllocator' used
        // to supply memory.  If 'basicAllocator' is 0, the currently installed
        // default allocator is used.

    //! ~Encoder() = default;
        // Destroy this object.

    // MANIPULATORS
    template <class TYPE>
    int encode(bsl::streambuf        *streamBuf,
               const TYPE&            value,
               const EncoderOptions&  options);
    template <class TYPE>
    int encode(bsl::streambuf       *streamBuf,
               const TYPE&           value,
               const EncoderOptions *options);
        // Encode the specified 'value', of (template parameter) 'TYPE', in the
        // JSON format using the specified 'options' and output it onto the
        // specified 'streamBuf'.  Specifying a nullptr 'options' is equivalent
        // to passing a default-constructed DecoderOptions in 'options'.
        // 'TYPE' shall be a 'bdlat'-compatible sequence, choice, or array
        // type, or a 'bdlat'-compatible dynamic type referring to one of those
        // types.  Return 0 on success, and a non-zero value otherwise.

    template <class TYPE>
    int encode(bsl::ostream&         stream,
               const TYPE&           value,
               const EncoderOptions& options);
    template <class TYPE>
    int encode(bsl::ostream&         stream,
               const TYPE&           value,
               const EncoderOptions *options);
        // Encode the specified 'value', of (template parameter) 'TYPE', in the
        // JSON format using the specified 'options' and output it onto the
        // specified 'stream'.  Specifying a nullptr 'options' is equivalent to
        // passing a default-constructed DecoderOptions in 'options'.  'TYPE'
        // shall be a 'bdlat'-compatible choice, or array type, or a
        // 'bdlat'-compatible dynamic type referring to one of those types.
        // Return 0 on success, and a non-zero value otherwise.

    template <class TYPE>
    int encode(bsl::streambuf *streamBuf, const TYPE& value);
        // Encode the specified 'value' of (template parameter) 'TYPE' into the
        // specified 'streamBuf'.  Return 0 on success, and a non-zero value
        // otherwise.
        //
        // DEPRECATED: Use the 'encode' function passed a reference to a
        // non-modifiable 'EncoderOptions' object instead.

    template <class TYPE>
    int encode(bsl::ostream& stream, const TYPE& value);
        // Encode the specified 'value' of (template parameter) 'TYPE' into the
        // specified 'stream'.  Return 0 on success, and a non-zero value
        // otherwise.  Note that 'stream' will be invalidated if the encoding
        // failed.
        //
        // DEPRECATED: Use the 'encode' function passed a reference to a
        // non-modifiable 'EncoderOptions' object instead.

    // ACCESSORS
    bsl::string loggedMessages() const;
        // Return a string containing any error, warning, or trace messages
        // that were logged during the last call to the 'encode' method.  The
        // log is reset each time 'encode' is called.
};

                       // =============================
                       // struct Encoder_EncodeImplUtil
                       // =============================

struct Encoder_EncodeImplUtil {
    // This component-private utility 'struct' provides a suite of functions
    // that encode 'bdlat' types in the JSON format.

    // TYPES
    typedef Encoder_EncodeImplUtil ThisUtil;
        // 'ThisUtil' is a convenience alias for this utility 'struct'.

    typedef int FormattingMode;
        // '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.

    // CLASS METHODS

    // Document Encoding

    static void openDocument(bsl::ostream          *outputStream,
                             const EncoderOptions&  options);
        // Print the sequence of characters that designate the start of a JSON
        // document to the specified 'outputStream' according to the specified
        // encoding 'options'.  See the component-level documentation of
        // {'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 closeDocument(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 component-level documentation of
        // {'baljsn_encoderoptions'} for a description of the effects, if any,
        // of each option in the 'options' on the end of a JSON document.

    // Value Encoding

    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'.  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 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 a description of the available
        // 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 '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 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>
    static int encode(bool                  *isValueEmpty,
                      Formatter             *formatter,
                      bsl::ostream          *logStream,
                      const TYPE&            value,
                      FormattingMode         formattingMode,
                      const EncoderOptions&  options,
                      bool                   isFirstMember);
        // 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 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.

    // Validation

    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' 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 validateChoice(bsl::ostream *logStream, const TYPE& value);
        // 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'.

    // Encoding Values That Have Specific Type Categories

    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.

    template <class TYPE>
    static int encodeSimpleValue(Formatter             *formatter,
                                 const TYPE&            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 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.

    // Encoding Prefixes and Suffixes

    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 "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 encodeObjectSuffix(bool           *isSuffixEmpty,
                                   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.

    // Encoding Arrays That Have Specific Shapes

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

    template <class TYPE>
    static int encodeNonEmptyArray(Formatter             *formatter,
                                   bsl::ostream          *logStream,
                                   const TYPE&            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'.  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 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.

    // Encoding Generalized Members

    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);
        // 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 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.

    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);
        // 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.
};

                       // ==============================
                       // struct Encoder_ValueDispatcher
                       // ==============================

class Encoder_ValueDispatcher {
    // 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.

  public:
    // TYPES
    typedef int FormattingMode;
        // '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.

  private:
    // DATA
    Formatter            *d_formatter_p;
        // wrapper around the output stream that determines the whitespace to
        // emit around each JSON token

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

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

    FormattingMode        d_formattingMode;
        // formatting mode of the value

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

  public:
    // CREATORS
    Encoder_ValueDispatcher(Formatter             *formatter,
                            bsl::ostream          *logStream,
                            FormattingMode         formattingMode,
                            bool                   isNextObjectFirst,
                            const EncoderOptions&  options);
        // Construct an 'Encoder_ValueDispatcher' object having the specified
        // 'formatter', 'logStream', 'formattingMode', 'isNextObjectFirst', and
        // 'options' attributes.

    // MANIPULATORS
    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);
        // 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 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&, bslmf::Nil);
        // The behavior of this function is undefined.

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

                        // ============================
                        // class Encoder_ElementVisitor
                        // ============================

class Encoder_ElementVisitor {
    // 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.

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

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

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

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

  public:
    // CREATORS
    Encoder_ElementVisitor(Formatter             *formatter,
                           bsl::ostream          *logStream,
                           bool                   isNextElementFirst,
                           const EncoderOptions&  options);
        // Construct an 'Encoder_ElementVisitor' object having the specified
        // 'formatter', 'logStream', 'isNextElementFirst', and 'options'
        // attributes.

    // MANIPULATORS
    template <class TYPE>
    int operator()(const TYPE& element);
        // 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.

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

                      // ===============================
                      // class Encoder_ElementDispatcher
                      // ===============================

class Encoder_ElementDispatcher {
    // 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.

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

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

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

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

  public:
    // CREATORS
    Encoder_ElementDispatcher(Formatter             *formatter,
                              bsl::ostream          *logStream,
                              bool                   isNextElementFirst,
                              const EncoderOptions&  options);
        // Construct an 'Encoder_ElementDispatcher' object having the specified
        // 'formatter', 'logStream', 'isNextElementFirst', and 'options'
        // attributes.

    // MANIPULATORS
    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);
        // 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 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&, bslmf::Nil);
        // The behavior of this function is undefined.

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

                       // ==============================
                       // class Encoder_SelectionVisitor
                       // ==============================

class Encoder_SelectionVisitor {
    // 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.

  public:
    // TYPES
    typedef int FormattingMode;
        // '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.

  private:
    // DATA
    Formatter            *d_formatter_p;
        // wrapper around the output stream that determines the whitespace to
        // emit around each JSON token

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

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

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

  public:
    // CREATORS
    Encoder_SelectionVisitor(Formatter             *formatter,
                             bsl::ostream          *logStream,
                             bool                   isNextObjectFirst,
                             const EncoderOptions&  options);
        // Construct an 'Encoder_SelectionVisitor' object having the specified
        // 'formatter', 'logStream', 'isNextObjectFirst', and 'options'
        // attributes.

    // MANIPULATORS
    template <class TYPE, class SELECTION_INFO>
    int operator()(const TYPE& selection, const SELECTION_INFO& selectionInfo);
        // 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.

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

                     // =================================
                     // class Encoder_SelectionDispatcher
                     // =================================

class Encoder_SelectionDispatcher {
    // 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.

  public:
    // TYPES
    typedef int FormattingMode;
        // '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.

  private:
    // DATA
    Formatter            *d_formatter_p;
        // wrapper around the output stream that determines the whitespace to
        // emit around each JSON token

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

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

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

    FormattingMode        d_formattingMode;
        // formatting mode of the selection

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

  public:
    // CREATORS
    Encoder_SelectionDispatcher(Formatter               *formatter,
                                bsl::ostream            *logStream,
                                const bsl::string_view&  selectionName,
                                FormattingMode           formattingMode,
                                bool                     isNextObjectFirst,
                                const EncoderOptions&    options);
        // Construct an 'Encoder_SelectionDispatcher' object having the
        // specified 'formatter', 'logStream', and 'options' attributes.

    // MANIPULATORS
    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);
        // 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&, bslmf::Nil);
        // The behavior of this function is undefined.

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

                       // ==============================
                       // class Encoder_AttributeVisitor
                       // ==============================

class Encoder_AttributeVisitor {
    // 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.

  public:
    // TYPES
    typedef int FormattingMode;
        // '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.

  private:
    // DATA
    Formatter            *d_formatter_p;
        // wrapper around the output stream that determines the whitespace to
        // emit around each JSON token

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

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

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

  public:
    // CREATORS
    Encoder_AttributeVisitor(Formatter             *formatter,
                             bsl::ostream          *logStream,
                             bool                   isNextAttributeFirst,
                             const EncoderOptions&  options);
        // Construct an 'Encoder_AttributeVisitor' object having the specified
        // 'formatter', 'logStream', 'isNextAttributeFirst', and 'options'
        // attributes.

    // MANIPULATORS
    template <class TYPE, class ATTRIBUTE_INFO>
    int operator()(const TYPE& attribute, const ATTRIBUTE_INFO& attributeInfo);
        // 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.

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

                     // =================================
                     // class Encoder_AttributeDispatcher
                     // =================================

class Encoder_AttributeDispatcher {
    // 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.

  public:
    // TYPES
    typedef int FormattingMode;
        // '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.

  private:
    // DATA
    Formatter            *d_formatter_p;
        // wrapper around the output stream that determines the whitespace to
        // emit around each JSON token

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

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

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

    FormattingMode        d_formattingMode;
        // formatting mode of the attribute

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

  public:
    // CREATORS
    Encoder_AttributeDispatcher(Formatter               *formatter,
                                bsl::ostream            *logStream,
                                const bsl::string_view&  attributeName,
                                FormattingMode           formattingMode,
                                bool                     isNextAttributeFirst,
                                const EncoderOptions&    options);
        // Construct an 'Encoder_AttributeDispatcher' object having the
        // specified 'formatter', 'logStream', 'attributeName',
        // 'formattingMode', 'isNextAttributeFirst', and 'options' attributes.

    // MANIPULATORS
    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);
        // 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.

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

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

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

                               // -------------
                               // class Encoder
                               // -------------

// PRIVATE MANIPULATORS
inline
bsl::ostream& Encoder::logStream()
{
    return d_logStream;
}

// CREATORS
inline
Encoder::Encoder(bslma::Allocator *basicAllocator)
: d_logStream(basicAllocator)
{
}

// MANIPULATORS
template <class TYPE>
inline
int Encoder::encode(bsl::streambuf *streamBuf, const TYPE& value)
{
    const EncoderOptions options;
    return encode(streamBuf, value, options);
}

template <class TYPE>
inline
int Encoder::encode(bsl::ostream& stream, const TYPE& value)
{
    const EncoderOptions options;
    return encode(stream, value, options);
}

template <class TYPE>
int Encoder::encode(bsl::streambuf        *streamBuf,
                    const TYPE&            value,
                    const EncoderOptions&  options)
{
    BSLS_ASSERT(streamBuf);

    d_logStream.clear();
    d_logStream.str("");

    bdlat_TypeCategory::Value category =
                                    bdlat_TypeCategoryFunctions::select(value);
    if (bdlat_TypeCategory::e_SEQUENCE_CATEGORY != category &&
        bdlat_TypeCategory::e_CHOICE_CATEGORY != category &&
        bdlat_TypeCategory::e_ARRAY_CATEGORY != category) {
        logStream()
            << "Encoded object must be a Sequence, Choice, or Array type."
            << bsl::endl;
        return -1;                                                    // RETURN
    }

    bsl::ostream outputStream(streamBuf);
    Encoder_EncodeImplUtil::openDocument(&outputStream, options);

    const int rc = Encoder_EncodeImplUtil::encode(&d_logStream,
                                                  &outputStream,
                                                  value,
                                                  options);
    if (0 != rc) {
        streamBuf->pubsync();
        return rc;                                                    // RETURN
    }

    Encoder_EncodeImplUtil::closeDocument(&outputStream, options);

    if (!outputStream) {
        logStream()
            << "An error occurred when writing to the supplied output stream"
               " or stream buffer."
            << bsl::endl;
        streamBuf->pubsync();
        return -1;                                                    // RETURN
    }

    streamBuf->pubsync();

    return 0;
}

template <class TYPE>
int Encoder::encode(bsl::streambuf       *streamBuf,
                    const TYPE&           value,
                    const EncoderOptions *options)
{
    EncoderOptions localOpts;
    return encode(streamBuf, value, options ? *options : localOpts);
}

template <class TYPE>
int Encoder::encode(bsl::ostream&         stream,
                    const TYPE&           value,
                    const EncoderOptions& options)
{
    if (!stream.good()) {
        logStream() << "Invalid stream." << bsl::endl;
        return -1;                                                    // RETURN
    }

    const int rc = this->encode(stream.rdbuf(), value, options);
    if (rc) {
        stream.setstate(bsl::ios_base::failbit);
        return rc;                                                    // RETURN
    }

    return 0;
}

template <class TYPE>
inline
int Encoder::encode(bsl::ostream&         stream,
                    const TYPE&           value,
                    const EncoderOptions *options)
{
    EncoderOptions localOpts;
    return encode(stream, value, options ? *options : localOpts);
}

// ACCESSORS
inline
bsl::string Encoder::loggedMessages() const
{
    return d_logStream.str();
}

                       // -----------------------------
                       // struct Encoder_EncodeImplUtil
                       // -----------------------------

// CLASS METHODS

// Document Encoding Functions

inline
void Encoder_EncodeImplUtil::openDocument(bsl::ostream          *outputStream,
                                          const EncoderOptions&  options)
{
    if (baljsn::EncoderOptions::e_PRETTY == options.encodingStyle()) {
        bdlb::Print::indent(*outputStream,
                            options.initialIndentLevel(),
                            options.spacesPerLevel());
    }
}

inline
void Encoder_EncodeImplUtil::closeDocument(
                                   bsl::ostream                  *outputStream,
                                   const baljsn::EncoderOptions&  options)
{
    if (baljsn::EncoderOptions::e_PRETTY == options.encodingStyle()) {
        (*outputStream) << '\n';
    }
}

// Value Encoding

template <class TYPE>
inline
int Encoder_EncodeImplUtil::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 TYPE>
inline
int Encoder_EncodeImplUtil::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;

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

    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 TYPE>
int Encoder_EncodeImplUtil::encode(bool                  *isValueEmpty,
                                   Formatter             *formatter,
                                   bsl::ostream          *logStream,
                                   const TYPE&            value,
                                   FormattingMode         formattingMode,
                                   const EncoderOptions&  options,
                                   bool                   isFirstMember)
{
    Encoder_ValueDispatcher 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 TYPE>
int Encoder_EncodeImplUtil::validate(bsl::ostream               *logStream,
                                     const TYPE&                 value,
                                     bdlat_TypeCategory::Choice)
{
    return validateChoice(logStream, value);
}

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

template <class TYPE>
int Encoder_EncodeImplUtil::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 TYPE>
inline
int Encoder_EncodeImplUtil::encodeSimpleValue(Formatter             *formatter,
                                              const TYPE&            value,
                                              const EncoderOptions&  options)
{
    return formatter->putValue(value, &options);
}

// Encoding Value Prefixes and Suffixes

inline
void Encoder_EncodeImplUtil::encodeObjectPrefix(bool           *isPrefixEmpty,
                                                Formatter      *formatter,
                                                FormattingMode  formattingMode)
{
    if (bdlat_FormattingMode::e_UNTAGGED & formattingMode) {
        *isPrefixEmpty = true;
        return;                                                       // RETURN
    }

    formatter->openObject();

    *isPrefixEmpty = false;
}

inline
void Encoder_EncodeImplUtil::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

inline
void Encoder_EncodeImplUtil::encodeEmptyArray(Formatter *formatter)
{
    formatter->openArray(true);
    formatter->closeArray(true);
}

template <class TYPE>
int Encoder_EncodeImplUtil::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();

    Encoder_ElementVisitor 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

template <class TYPE>
int Encoder_EncodeImplUtil::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)
    ///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.
{
    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 TYPE, class OTHER_CATEGORY>
int Encoder_EncodeImplUtil::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;
}

inline
int Encoder_EncodeImplUtil::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;
}

inline
int Encoder_EncodeImplUtil::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);
}

inline
int Encoder_EncodeImplUtil::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 Encoder_ValueDispatcher
                       // ------------------------------

// CREATORS
inline
Encoder_ValueDispatcher::Encoder_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
inline
int Encoder_ValueDispatcher::operator()(const bsl::vector<char>&  value,
                                        bdlat_TypeCategory::Array)
{
    d_isNextObjectFirst = false;
    return Encoder_EncodeImplUtil::encodeCharArray(d_formatter_p,
                                                   value,
                                                   *d_options_p);
}

template <class TYPE>
inline
int Encoder_ValueDispatcher::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()) {
        Encoder_EncodeImplUtil::encodeEmptyArray(d_formatter_p);
        d_isNextObjectFirst = false;
        return 0;                                                     // RETURN
    }

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

    d_isNextObjectFirst = false;
    return 0;
}

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

    bool isPrefixEmpty = false;
    Encoder_EncodeImplUtil::encodeObjectPrefix(&isPrefixEmpty,
                                               d_formatter_p,
                                               d_formattingMode);

    Encoder_SelectionVisitor 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;
    Encoder_EncodeImplUtil::encodeObjectSuffix(&isSuffixEmpty,
                                               d_formatter_p,
                                               d_formattingMode);

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

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

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

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

    d_isNextObjectFirst = false;
    return Encoder_EncodeImplUtil::encodeSimpleValue(d_formatter_p,
                                                     valueString,
                                                     *d_options_p);
}

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

    Encoder_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 TYPE>
inline
int Encoder_ValueDispatcher::operator()(const TYPE&                  value,
                                        bdlat_TypeCategory::Sequence)
{
    bool isPrefixEmpty = false;
    Encoder_EncodeImplUtil::encodeObjectPrefix(&isPrefixEmpty,
                                               d_formatter_p,
                                               d_formattingMode);

    Encoder_AttributeVisitor 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;
    Encoder_EncodeImplUtil::encodeObjectSuffix(&isSuffixEmpty,
                                               d_formatter_p,
                                               d_formattingMode);

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

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

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

// ACCESSORS
inline
bool Encoder_ValueDispatcher::isNextObjectFirst() const
{
    return d_isNextObjectFirst;
}

                        // ----------------------------
                        // class Encoder_ElementVisitor
                        // ----------------------------

// CREATORS
inline
Encoder_ElementVisitor::Encoder_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 TYPE>
inline
int Encoder_ElementVisitor::operator()(const TYPE& element)
{
    Encoder_ElementDispatcher 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
inline
bool Encoder_ElementVisitor::isNextElementFirst() const
{
    return d_isNextElementFirst;
}

                      // -------------------------------
                      // class Encoder_ElementDispatcher
                      // -------------------------------

// CREATORS
inline
Encoder_ElementDispatcher::Encoder_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
inline
int Encoder_ElementDispatcher::operator()(const bsl::vector<char>&  element,
                                          bdlat_TypeCategory::Array)
{
    if (!d_isNextElementFirst) {
        d_formatter_p->addArrayElementSeparator();
    }

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

    d_isNextElementFirst = false;
    return 0;
}

template <class TYPE>
int Encoder_ElementDispatcher::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()) {
        Encoder_EncodeImplUtil::encodeEmptyArray(d_formatter_p);
        d_isNextElementFirst = false;
        return 0;                                                     // RETURN
    }

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

    d_isNextElementFirst = false;
    return 0;
}

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

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

    d_formatter_p->openObject();

    Encoder_SelectionVisitor 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 TYPE>
int Encoder_ElementDispatcher::operator()(
                                    const TYPE&                        element,
                                    bdlat_TypeCategory::CustomizedType)
{
    return bdlat_TypeCategoryUtil::accessByCategory(
                     bdlat_CustomizedTypeFunctions::convertToBaseType(element),
                     *this);
}

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

template <class TYPE>
int Encoder_ElementDispatcher::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 Encoder_EncodeImplUtil::encodeSimpleValue(d_formatter_p,
                                                     valueString,
                                                     *d_options_p);
}

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

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

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

    Encoder_ElementVisitor 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 TYPE>
int Encoder_ElementDispatcher::operator()(const TYPE&                  element,
                                          bdlat_TypeCategory::Sequence)
{
    if (!d_isNextElementFirst) {
        d_formatter_p->addArrayElementSeparator();
    }

    d_formatter_p->openObject();

    Encoder_AttributeVisitor 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 TYPE>
int Encoder_ElementDispatcher::operator()(const TYPE&                element,
                                          bdlat_TypeCategory::Simple)
{
    if (!d_isNextElementFirst) {
        d_formatter_p->addArrayElementSeparator();
    }

    d_isNextElementFirst = false;
    return Encoder_EncodeImplUtil::encodeSimpleValue(d_formatter_p,
                                                     element,
                                                     *d_options_p);
}

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

// ACCESSORS
inline
bool Encoder_ElementDispatcher::isNextElementFirst() const
{
    return d_isNextElementFirst;
}

                       // ------------------------------
                       // class Encoder_SelectionVisitor
                       // ------------------------------

// CREATORS
inline
Encoder_SelectionVisitor::Encoder_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 TYPE, class SELECTION_INFO>
inline
int Encoder_SelectionVisitor::operator()(const TYPE&           selection,
                                         const SELECTION_INFO& selectionInfo)
{
    Encoder_SelectionDispatcher 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
inline
bool Encoder_SelectionVisitor::isNextObjectFirst() const
{
    return d_isNextObjectFirst;
}

                     // ---------------------------------
                     // class Encoder_SelectionDispatcher
                     // ---------------------------------

// CREATORS
inline
Encoder_SelectionDispatcher::Encoder_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 TYPE>
inline
int Encoder_SelectionDispatcher::operator()(
                                  const TYPE&                        selection,
                                  bdlat_TypeCategory::CustomizedType)
{
    return bdlat_TypeCategoryUtil::accessByCategory(
                   bdlat_CustomizedTypeFunctions::convertToBaseType(selection),
                   *this);
}

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

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

template <class TYPE>
inline
int Encoder_SelectionDispatcher::operator()(const TYPE&, bslmf::Nil)
{
    BSLS_ASSERT_OPT(!"Reachable");
    return -1;
}

// ACCESSORS
inline
bool Encoder_SelectionDispatcher::isNextObjectFirst() const
{
    return d_isNextObjectFirst;
}

                       // ------------------------------
                       // class Encoder_AttributeVisitor
                       // ------------------------------

// CREATORS
inline
Encoder_AttributeVisitor::Encoder_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 TYPE, class ATTRIBUTE_INFO>
int Encoder_AttributeVisitor::operator()(const TYPE&           attribute,
                                         const ATTRIBUTE_INFO& attributeInfo)
{
    Encoder_AttributeDispatcher 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
inline
bool Encoder_AttributeVisitor::isNextAttributeFirst() const
{
    return d_isNextAttributeFirst;
}

                     // ---------------------------------
                     // class Encoder_AttributeDispatcher
                     // ---------------------------------

// CREATORS
inline
Encoder_AttributeDispatcher::Encoder_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
inline
int Encoder_AttributeDispatcher::operator()(
                                           const bsl::vector<char>&  attribute,
                                           bdlat_TypeCategory::Array category)
{
    return Encoder_EncodeImplUtil::encodeMember(&d_isNextAttributeFirst,
                                                d_formatter_p,
                                                d_logStream_p,
                                                d_attributeName.data(),
                                                attribute,
                                                d_formattingMode,
                                                *d_options_p,
                                                d_isNextAttributeFirst,
                                                category);
}

template <class TYPE>
inline
int Encoder_AttributeDispatcher::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 Encoder_EncodeImplUtil::encodeMember(&d_isNextAttributeFirst,
                                                d_formatter_p,
                                                d_logStream_p,
                                                d_attributeName.data(),
                                                attribute,
                                                d_formattingMode,
                                                *d_options_p,
                                                d_isNextAttributeFirst,
                                                category);
}

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

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

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

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

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

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

// ACCESSORS
inline
bool Encoder_AttributeDispatcher::isNextAttributeFirst() const
{
    return d_isNextAttributeFirst;
}

// The 'Encoder_Formatter' 'class' has been replaced by the 'baljsn::Formatter'
// 'class' in the 'baljsn_formatter' component.  Clients should use that
// 'class' instead.  The following 'class' definition is provided for
// backwards-compatibility for users that have written code using this
// component-private 'class'.

                          // =======================
                          // class Encoder_Formatter
                          // =======================

class Encoder_Formatter {
    // This class implements a formatter providing operations for rending JSON
    // text elements to an output stream (supplied at construction) according
    // to a set of formatting options (also supplied at construction).  This is
    // a component-private class and should not be used outside of this
    // component.
    //
    // DEPRECATED: Use 'baljsn::Formatter' instead.

    // DATA
    bsl::ostream& d_outputStream;    // stream for output (held, not owned)
    bool          d_usePrettyStyle;  // encoding style
    int           d_indentLevel;     // initial indent level
    int           d_spacesPerLevel;  // spaces per level
    bool          d_isArrayElement;  // is current element part of an array

  public:
    // CREATORS
    Encoder_Formatter(bsl::ostream& stream, const EncoderOptions& options);
        // Create a 'Encoder_Formatter' object using the specified 'stream' and
        // 'options'.

    //! ~Encoder_Formatter() = default;
        // Destroy this object.

    // MANIPULATORS
    void openObject();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of an object.

    void closeObject();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the end of an object.

    void openArray(bool formatAsEmptyArrayFlag = false);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of an array.  Optionally specify
        // 'formatAsEmptyArrayFlag' denoting if the array being opened should
        // be formatted as an empty array.  If 'formatAsEmptyArrayFlag' is not
        // specified then the array being opened is formatted as an array
        // having elements.  Note that the formatting (and as a consequence the
        // 'formatAsEmptyArrayFlag') is relevant only if this formatter encodes
        // in the pretty style and is ignored otherwise.

    void closeArray(bool formatAsEmptyArrayFlag = false);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the end of an array.  Optionally specify
        // 'formatAsEmptyArrayFlag' denoting if the array being closed should
        // be formatted as an empty array.  If 'formatAsEmptyArrayFlag' is not
        // specified then the array being closed is formatted as an array
        // having elements.  Note that the formatting (and as a consequence the
        // 'formatAsEmptyArrayFlag') is relevant only if this formatter encodes
        // in the pretty style and is ignored otherwise.

    void indent();
        // Print onto the stream supplied at construction the sequence of
        // whitespace characters for the proper indentation of an element given
        // the encoding options supplied at construction.

    int openElement(const bsl::string& name);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of an element having the specified
        // 'name'.  Return 0 on success and a non-zero value otherwise.

    void closeElement();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the end of an element.

    void openDocument();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of the document.

    void closeDocument();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the end of the document.

    void setIsArrayElement(bool isArrayElement);
        // Set the flag denoting if the current element refers to an array
        // element to the specified 'isArrayElement'.

    // ACCESSORS
    bool isArrayElement() const;
        // Return the value of the flag denoting if the current element refers
        // to an array element.
};

                          // -----------------------
                          // class Encoder_Formatter
                          // -----------------------

// MANIPULATORS
inline
void Encoder_Formatter::setIsArrayElement(bool isArrayElement)
{
    d_isArrayElement = isArrayElement;
}

// ACCESSORS
inline
bool Encoder_Formatter::isArrayElement() const
{
    return d_isArrayElement;
}

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

#endif

// ----------------------------------------------------------------------------
// Copyright 2015 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 ----------------------------------