// 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:
//
// * one that writes to a `bsl::streambuf`
// * one that writes to an `bsl::ostream`
//
// This component can be used with types that support the `bdlat` framework
// (see the `bdlat` package for details), which is a compile-time interface for
// manipulating struct-like and union-like objects.  In particular, types
// generated by the `bas_codegen.pl` tool, and other dynamic types, can be
// encoded using this `class`.  The `encode` function can be invoked on any
// object that satisfies the requirements of a sequence, choice, or array
// object as defined in the `bdlat_sequencefunctions`, `bdlat_choicefunctions`,
// and `bdlat_arrayfunctions` components.
//
// Although the JSON format is easy to read and write and is very useful for
// debugging, it is relatively expensive to encode and decode and relatively
// bulky to transmit.  It is more efficient to use a binary encoding (such as
// BER) if the encoding format is under your control (see `balber_berencoder`).
//
// Refer to the details of the JSON encoding format supported by this encoder
// in the package documentation file (doc/baljsn.txt).
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: 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_encodeimplutil.h>
#include <baljsn_encoderoptions.h>
#include <baljsn_formatter.h>

#include <bdlar_refutil.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
                               // =============

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

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

    // NOT IMPLEMENTED
    Encoder(const Encoder&);

    // PRIVATE MANIPULATORS

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

  public:
    // CREATORS

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

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

    // MANIPULATORS

    /// 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 EncoderOptions 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::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 `stream`.  Specifying a nullptr `options` is equivalent to
    /// passing a default-constructed EncoderOptions 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::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` 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::streambuf *streamBuf, 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.
    template <class TYPE>
    int encode(bsl::ostream& stream, const TYPE& value);

    /// 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.  Note that this
    /// function behaves identically to `encode`, but does not instantiate any
    /// templates at compile time at the expense of being slightly slower at
    /// runtime; see the `baljsn` package documentation for more details.
    template <class TYPE>
    int encodeAny(bsl::streambuf        *streamBuf,
                  const TYPE&            value,
                  const EncoderOptions&  options);
    template <class TYPE>
    int encodeAny(bsl::streambuf       *streamBuf,
                  const TYPE&           value,
                  const EncoderOptions *options = 0);
    int encodeAny(bsl::streambuf            *streamBuf,
                  const bdlar::AnyConstRef&  any,
                  const EncoderOptions&      options);
    int encodeAny(bsl::streambuf            *streamBuf,
                  const bdlar::AnyConstRef&  any,
                  const EncoderOptions      *options = 0);

    /// 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.  Note that this
    /// function behaves identically to `encode`, but does not instantiate any
    /// templates at compile time at the expense of being slightly slower at
    /// runtime; see the `baljsn` package documentation for more details.
    template <class TYPE>
    int encodeAny(bsl::ostream&         stream,
                  const TYPE&           value,
                  const EncoderOptions& options);
    template <class TYPE>
    int encodeAny(bsl::ostream&         stream,
                  const TYPE&           value,
                  const EncoderOptions *options = 0);
    int encodeAny(bsl::ostream&             stream,
                  const bdlar::AnyConstRef& any,
                  const EncoderOptions&     options);
    int encodeAny(bsl::ostream&              stream,
                  const bdlar::AnyConstRef&  any,
                  const EncoderOptions      *options = 0);

    // ACCESSORS

    /// 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.
    bsl::string loggedMessages() const;
};

// ============================================================================
//                            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);
    EncodeImplUtil<Formatter>::openDocument(&outputStream, options);

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

    EncodeImplUtil<Formatter>::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);
}

template <class TYPE>
inline
int Encoder::encodeAny(bsl::streambuf        *streamBuf,
                       const TYPE&            value,
                       const EncoderOptions&  options)
{
    return encodeAny(streamBuf,
                     bdlar::RefUtil::makeAnyConstRef(value),
                     options);
}

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

inline
int Encoder::encodeAny(bsl::streambuf            *streamBuf,
                       const bdlar::AnyConstRef&  any,
                       const EncoderOptions      *options)
{
    return encodeAny(streamBuf, any, options ? *options : EncoderOptions());
}

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

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

inline
int Encoder::encodeAny(bsl::ostream&              stream,
                       const bdlar::AnyConstRef&  any,
                       const EncoderOptions      *options)
{
    return encodeAny(stream, any, options ? *options : EncoderOptions());
}

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

// 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
                          // =======================

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

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

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

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

    // MANIPULATORS

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

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

    /// 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 openArray(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 closeArray(bool formatAsEmptyArrayFlag = false);

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

    /// 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.
    int openElement(const bsl::string& name);

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

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

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

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

    // ACCESSORS

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

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