// balber_berencoder.h                                                -*-C++-*-

// ----------------------------------------------------------------------------
//                                   NOTICE
//
// This component is not up to date with current BDE coding standards, and
// should not be used as an example for new development.
// ----------------------------------------------------------------------------

#ifndef INCLUDED_BALBER_BERENCODER
#define INCLUDED_BALBER_BERENCODER

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

//@PURPOSE: Provide a BER encoder class.
//
//@CLASSES:
//   balber::BerEncoder: BER encoder
//
//@SEE_ALSO: balber_berdecoder, bdem_bdemencoder, balxml_encoder
//
//@DESCRIPTION: This component defines a single class, 'balber::BerEncoder',
// that contains a parameterized 'encode' function.  The 'encode' function
// encodes data read from a specified stream and loads the corresponding object
// to an object of the parameterized type.  The 'encode' method is overloaded
// for two types of input streams:
//: o 'bsl::streambuf'
//: o 'bsl::istream'
//
// This component encodes objects based on the X.690 BER specification.  It can
// only be used with types supported by the 'bdlat' framework.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Encoding an Employee Record
/// - - - - - - - - - - - - - - - - - - -
// Suppose that an "employee record" consists of a sequence of attributes --
// 'name', 'age', and 'salary' -- that are of types 'bsl::string', 'int', and
// 'float', respectively.  Furthermore, we have a need to BER encode employee
// records as a sequence of values (for out-of-process consumption).
//
// Assume that we have defined a 'usage::EmployeeRecord' class to represent
// employee record values, and assume that we have provided the 'bdlat'
// specializations that allow the 'balber' codec components to represent class
// values as a sequence of BER primitive values.  See
// {'bdlat_sequencefunctions'|Usage} for details of creating specializations
// for a sequence type.
//
// First, we create an employee record object having typical values:
//..
//  usage::EmployeeRecord bob("Bob", 56, 1234.00);
//  assert("Bob"   == bob.name());
//  assert(  56    == bob.age());
//  assert(1234.00 == bob.salary());
//..
// Now, we create a 'balber::Encoder' object and use it to encode our 'bob'
// object.  Here, to facilitate the examination of our results, the BER
// encoding data is delivered to a 'bslsb::MemOutStreamBuf' object:
//..
//  bdlsb::MemOutStreamBuf osb;
//  balber::BerEncoder     encoder;
//  int                    rc = encoder.encode(&osb, bob);
//  assert( 0 == rc);
//  assert(18 == osb.length());
//..
// Finally, we confirm that the generated BER encoding has the expected layout
// and values.  We create an 'bdlsb::FixedMemInStreamBuf' to manage our access
// to the data portion of the 'bdlsb::MemOutStreamBuf' where our BER encoding
// resides:
//..
//  bdlsb::FixedMemInStreamBuf isb(osb.data(), osb.length());
//..
// The 'balber_berutil' component provides functions that allow us to decode
// the descriptive fields and values of the BER encoded sequence:
//..
//  balber::BerConstants::TagClass tagClass;
//  balber::BerConstants::TagType  tagType;
//  int                            tagNumber;
//  int                            accumNumBytesConsumed = 0;
//  int                            length;
//
//  rc = balber::BerUtil::getIdentifierOctets(&isb,
//                                            &tagClass,
//                                            &tagType,
//                                            &tagNumber,
//                                            &accumNumBytesConsumed);
//  assert(0                                             == rc);
//  assert(balber::BerConstants::e_UNIVERSAL             == tagClass);
//  assert(balber::BerConstants::e_CONSTRUCTED           == tagType);
//  assert(balber::BerUniversalTagNumber::e_BER_SEQUENCE == tagNumber);
//
//  rc = balber::BerUtil::getLength(&isb, &length, &accumNumBytesConsumed);
//  assert(0                                    == rc);
//  assert(balber::BerUtil::k_INDEFINITE_LENGTH == length);
//..
// The 'UNIVERSAL' value in 'tagClass' indicates that the 'tagNumber' value
// represents a type in the BER standard, a 'BER_SEQUENCE', as we requested of
// the infrastructure (see the 'IsSequence' specialization above).  The
// 'tagType' value of 'CONSTRUCTED' indicates that this is a non-primitive
// type.  The 'INDEFINITE' value for length is typical for sequence encodings.
// In these cases, the end-of-data is indicated by a sequence to two null
// bytes.
//
// We now examine the tags and values corresponding to each of the data members
// of 'usage::EmployeeRecord' class.  For each of these the 'tagClass' is
// 'CONTEXT_SPECIFIC' (i.e., member of a larger construct) and the 'tagType' is
// 'PRIMITIVE' ('bsl::string', 'int', and 'float' each correspond to a
// primitive BER type.  The 'tagNumber' for each field was defined (in the
// elided definiton) to correspond the position of the field in the
// 'usage::EmployeeRecord' class.
//..
//  rc = balber::BerUtil::getIdentifierOctets(&isb,
//                                            &tagClass,
//                                            &tagType,
//                                            &tagNumber,
//                                            &accumNumBytesConsumed);
//  assert(0                                        == rc);
//  assert(balber::BerConstants::e_CONTEXT_SPECIFIC == tagClass);
//  assert(balber::BerConstants::e_PRIMITIVE        == tagType);
//  assert(1                                        == tagNumber);
//
//  bsl::string name;
//  rc = balber::BerUtil::getValue(&isb, &name, &accumNumBytesConsumed);
//  assert(0     == rc);
//  assert("Bob" == name);
//
//  rc = balber::BerUtil::getIdentifierOctets(&isb,
//                                            &tagClass,
//                                            &tagType,
//                                            &tagNumber,
//                                            &accumNumBytesConsumed);
//  assert(0                                        == rc);
//  assert(balber::BerConstants::e_CONTEXT_SPECIFIC == tagClass);
//  assert(balber::BerConstants::e_PRIMITIVE        == tagType);
//  assert(2                                        == tagNumber);
//
//  int age = 0;
//  rc = balber::BerUtil::getValue(&isb, &age, &accumNumBytesConsumed);
//  assert(0  == rc);
//  assert(56 == age);
//
//  rc = balber::BerUtil::getIdentifierOctets(&isb,
//                                            &tagClass,
//                                            &tagType,
//                                            &tagNumber,
//                                            &accumNumBytesConsumed);
//  assert(0 == rc);
//  assert(balber::BerConstants::e_CONTEXT_SPECIFIC == tagClass);
//  assert(balber::BerConstants::e_PRIMITIVE        == tagType);
//  assert(3                                        == tagNumber);
//
//  float salary = 0.0;
//  rc = balber::BerUtil::getValue(&isb, &salary, &accumNumBytesConsumed);
//  assert(0       == rc);
//  assert(1234.00 == salary);
//..
// Lastly, we confirm that end-of-data sequence (two null bytes) are found we
// expect them and that we have entirely consumed the data that we generated by
// our encoding.
//..
//  rc = balber::BerUtil::getEndOfContentOctets(&isb, &accumNumBytesConsumed);
//  assert(0            == rc);
//  assert(osb.length() == static_cast<bsl::size_t>(accumNumBytesConsumed));
//..

#include <balscm_version.h>

#include <balber_berconstants.h>
#include <balber_berencoderoptions.h>
#include <balber_beruniversaltagnumber.h>
#include <balber_berutil.h>

#include <bdlat_arrayfunctions.h>
#include <bdlat_attributeinfo.h>
#include <bdlat_choicefunctions.h>
#include <bdlat_customizedtypefunctions.h>
#include <bdlat_enumfunctions.h>
#include <bdlat_formattingmode.h>
#include <bdlat_nullablevaluefunctions.h>
#include <bdlat_sequencefunctions.h>
#include <bdlat_typecategory.h>
#include <bdlat_typename.h>

#include <bslma_allocator.h>

#include <bsl_string.h>

#include <bdlsb_memoutstreambuf.h>

#include <bsls_objectbuffer.h>

#include <bsl_ostream.h>
#include <bsl_vector.h>
#include <bsl_typeinfo.h>

#include <bsls_assert.h>

namespace BloombergLP {
namespace balber {

struct BerEncoder_encodeProxy;
class  BerEncoder_Visitor;
class  BerEncoder_UniversalElementVisitor;
class  BerEncoder_LevelGuard;

                              // ================
                              // class BerEncoder
                              // ================

class BerEncoder {
    // This class contains the parameterized 'encode' functions that encode
    // 'bdlat' types to an outgoing stream in BER format.

  private:
    // FRIENDS
    friend struct BerEncoder_encodeProxy;
    friend class  BerEncoder_Visitor;
    friend class  BerEncoder_UniversalElementVisitor;
    friend class  BerEncoder_LevelGuard;

    // PRIVATE TYPES
    class MemOutStream : public bsl::ostream {
        // This class provides stream for logging using
        // 'bdlsb::MemOutStreamBuf' as a streambuf.  The logging stream is
        // created on demand, i.e., during the first attempt to log message.

        // DATA
        bdlsb::MemOutStreamBuf d_sb;

        // NOT IMPLEMENTED
        MemOutStream(const MemOutStream&);             // = delete;
        MemOutStream& operator=(const MemOutStream&);  // = delete;

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

        virtual ~MemOutStream();
            // Destroy this stream and release memory back to the allocator.
            //
            // Although the compiler should generate this destructor
            // implicitly, xlC 8 breaks when the destructor is called by name
            // unless it is explicitly declared.

        // MANIPULATORS
        void reset();
            // Reset the internal streambuf to empty.

        // ACCESSORS
        const char *data() const;
            // Return the address of the memory containing the values formatted
            // to this stream.  The data is not null-terminated unless a null
            // character was appended onto this stream.

        int length() const;
            // Return the length of the formatted data, including null
            // characters appended to the stream, if any.
    };

  public:
    // PUBLIC TYPES
    enum ErrorSeverity {
        e_BER_SUCCESS = 0x00
      , e_BER_ERROR   = 0x02

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
      , BDEM_BER_SUCCESS = e_BER_SUCCESS
      , BDEM_BER_ERROR   = e_BER_ERROR
#endif  // BDE_OMIT_INTERNAL_DEPRECATED
    };

  private:
    // DATA
    const BerEncoderOptions          *d_options;        // held, not owned
    bslma::Allocator                 *d_allocator;      // held, not owned

    bsls::ObjectBuffer<MemOutStream>  d_logArea;
        // placeholder for MemOutStream

    MemOutStream                     *d_logStream;
        // if not zero, log stream was created at the moment of first logging
        // and must be destroyed

    ErrorSeverity                     d_severity;       // error severity

    bsl::streambuf                   *d_streamBuf;      // held, not owned
    int                               d_currentDepth;   // current depth

    // NOT IMPLEMENTED
    BerEncoder(const BerEncoder&);             // = delete;
    BerEncoder& operator=(const BerEncoder&);  // = delete;

    // PRIVATE MANIPULATORS
    ErrorSeverity logMsg(const char             *msg,
                         BerConstants::TagClass  tagClass,
                         int                     tagNumber,
                         const char             *name  =  0,
                         int                     index = -1);
        // Log the specified 'msg' using the specified 'tagClass', 'tagNumber',
        // name, and 'index', and return 'errorSeverity()'.

    ErrorSeverity logError(BerConstants::TagClass  tagClass,
                           int                     tagNumber,
                           const char             *name  =  0,
                           int                     index = -1);
        // Log error and upgrade the severity level.  Return 'errorSeverity()'.

    bsl::ostream& logStream();
        // Return the stream for logging.  Note the if stream has not been
        // created yet, it will be created during this call.

    int encodeImpl(const bsl::vector<char>&  value,
                   BerConstants::TagClass    tagClass,
                   int                       tagNumber,
                   int                       formattingMode,
                   bdlat_TypeCategory::Array );

    template <typename TYPE>
    int encodeArrayImpl(const TYPE&            value,
                        BerConstants::TagClass tagClass,
                        int                    tagNumber,
                        int                    formattingMode);

    template <typename TYPE>
    int encodeImpl(const TYPE&                value,
                   BerConstants::TagClass     tagClass,
                   int                        tagNumber,
                   int                        formattingMode,
                   bdlat_TypeCategory::Choice );

    template <typename TYPE>
    int encodeImpl(const TYPE&                       value,
                   BerConstants::TagClass            tagClass,
                   int                               tagNumber,
                   int                               formattingMode,
                   bdlat_TypeCategory::NullableValue );

    template <typename TYPE>
    int encodeImpl(const TYPE&                        value,
                   BerConstants::TagClass             tagClass,
                   int                                tagNumber,
                   int                                formattingMode,
                   bdlat_TypeCategory::CustomizedType );

    template <typename TYPE>
    int encodeImpl(const TYPE&                     value,
                   BerConstants::TagClass          tagClass,
                   int                             tagNumber,
                   int                             formattingMode,
                   bdlat_TypeCategory::Enumeration );

    template <typename TYPE>
    int encodeImpl(const TYPE&                  value,
                   BerConstants::TagClass       tagClass,
                   int                          tagNumber,
                   int                          formattingMode,
                   bdlat_TypeCategory::Sequence );

    template <typename TYPE>
    int encodeImpl(const TYPE&                value,
                   BerConstants::TagClass     tagClass,
                   int                        tagNumber,
                   int                        formattingMode,
                   bdlat_TypeCategory::Simple );

    template <typename TYPE>
    int encodeImpl(const TYPE&               value,
                   BerConstants::TagClass    tagClass,
                   int                       tagNumber,
                   int                       formattingMode,
                   bdlat_TypeCategory::Array );

    template <typename TYPE>
    int encodeImpl(const TYPE&                     value,
                   BerConstants::TagClass          tagClass,
                   int                             tagNumber,
                   int                             formattingMode,
                   bdlat_TypeCategory::DynamicType );

  public:
    // CREATORS
    BerEncoder(const BerEncoderOptions *options        = 0,
               bslma::Allocator        *basicAllocator = 0);
        // Construct an encoder object.  Optionally specify encoder 'options'.
        // If 'options' is 0, 'BerEncoderOptions()' is used.  Optionally
        // specify a 'basicAllocator' used to supply memory.  If
        // 'basicAllocator' is 0, the currently installed default allocator is
        // used.

    ~BerEncoder();
        // Destroy this object.  This destruction has no effect on objects
        // pointed-to by the pointers provided at construction.

    template <typename TYPE>
    int encode(bsl::streambuf *streamBuf, const TYPE& value);
        // Encode the specified non-modifiable 'value' to the specified
        // 'streamBuf'.  Return 0 on success, and a non-zero value otherwise.

    template <typename TYPE>
    int encode(bsl::ostream& stream, const TYPE& value);
        // Encode the specified non-modifiable 'value' to the specified
        // 'stream'.  Return 0 on success, and a non-zero value otherwise.  If
        // the encoding fails 'stream' will be invalidated.

    // ACCESSORS
    const BerEncoderOptions *options() const;
        // Return address of the options.

    ErrorSeverity  errorSeverity() const;
        // Return the severity of the most severe warning or error encountered
        // during the last call to the 'encode' method.  The severity is reset
        // each time 'encode' is called.

    bslstl::StringRef 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.
};

                    // ===================================
                    // private class BerEncoder_LevelGuard
                    // ===================================

class BerEncoder_LevelGuard {
    // This class serves the purpose to automatically increment-decrement the
    // current depth level.

    // DATA
    BerEncoder *d_encoder;

    // NOT IMPLEMENTED
    BerEncoder_LevelGuard(BerEncoder_LevelGuard&);            // = delete;
    BerEncoder_LevelGuard& operator=(BerEncoder_LevelGuard&); // = delete;

  public:
    // CREATORS
    BerEncoder_LevelGuard(BerEncoder *encoder);
    ~BerEncoder_LevelGuard();
};

                      // ================================
                      // private class BerEncoder_Visitor
                      // ================================

class BerEncoder_Visitor {
    // This class is used as a visitor for visiting contained objects during
    // encoding.  Produces always BER elements with CONTEXT_SPECIFIC BER tag.

    // DATA
    BerEncoder             *d_encoder;     // encoder to write data to
    BerEncoder_LevelGuard   d_levelGuard;

    // NOT IMPLEMENTED
    BerEncoder_Visitor(const BerEncoder_Visitor&);             // = delete;
    BerEncoder_Visitor& operator=(const BerEncoder_Visitor&);  // = delete;

  public:
    // CREATORS
    BerEncoder_Visitor(BerEncoder *encoder);
    ~BerEncoder_Visitor();

    // MANIPULATORS
    template <typename TYPE, typename INFO>
    int operator()(const TYPE& value, const INFO& info);
};

              // ================================================
              // private class BerEncoder_UniversalElementVisitor
              // ================================================

class BerEncoder_UniversalElementVisitor {
    // This class is used as a visitor for visiting the top-level element and
    // also array elements during encoding.  This class is required so that the
    // universal tag number of the element can be determined when the element
    // is visited.

    // PRIVATE DATA MEMBERS
    BerEncoder            *d_encoder;         // streambuf to write data to
    int                    d_formattingMode;  // formatting mode to use
    BerEncoder_LevelGuard  d_levelGuard;

  private:
    // NOT IMPLEMENTED
    BerEncoder_UniversalElementVisitor(
                                    const BerEncoder_UniversalElementVisitor&);
                                                                   // = delete;
    BerEncoder_UniversalElementVisitor& operator=(
                                    const BerEncoder_UniversalElementVisitor&);
                                                                   // = delete;
  public:
    // CREATORS
    BerEncoder_UniversalElementVisitor(BerEncoder *encoder,
                                       int         formattingMode);

    ~BerEncoder_UniversalElementVisitor();

    // MANIPULATORS
    template <typename TYPE>
    int operator()(const TYPE& value);
};

// ============================================================================
//                               PROXY CLASSES
// ============================================================================

                       // =============================
                       // struct BerEncoder_encodeProxy
                       // =============================

struct BerEncoder_encodeProxy {
    // Component-private struct.  Provides accessor that keeps current context
    // and can be used in different 'bdlat' Category Functions.

    // DATA MEMBERS
    BerEncoder             *d_encoder;
    BerConstants::TagClass  d_tagClass;
    int                     d_tagNumber;
    int                     d_formattingMode;

    // CREATORS Creators have been omitted to allow simple static
    // initialization of this struct.

    // FUNCTIONS
    template <typename TYPE>
    int operator()(const TYPE& object, bslmf::Nil);

    template <typename TYPE, typename ANY_CATEGORY>
    int operator()(const TYPE& object, ANY_CATEGORY category);

    template <typename TYPE>
    int operator()(const TYPE& object);
};

}  // close package namespace

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

                   // --------------------------------------
                   // class balber::BerEncoder::MemOutStream
                   // --------------------------------------

inline
balber::BerEncoder::MemOutStream::MemOutStream(
                                              bslma::Allocator *basicAllocator)
: bsl::ostream(0)
, d_sb(bslma::Default::allocator(basicAllocator))
{
    rdbuf(&d_sb);
}

// MANIPULATORS
inline
void balber::BerEncoder::MemOutStream::reset()
{
    d_sb.reset();
}

// ACCESSORS
inline
const char *balber::BerEncoder::MemOutStream::data() const
{
    return d_sb.data();
}

inline
int balber::BerEncoder::MemOutStream::length() const
{
    return static_cast<int>(d_sb.length());
}

namespace balber {

                        // ----------------------------
                        // class BerEncoder::LevelGuard
                        // ----------------------------

inline
BerEncoder_LevelGuard::BerEncoder_LevelGuard(BerEncoder *encoder)
: d_encoder (encoder)
{
    ++d_encoder->d_currentDepth;
}

inline
BerEncoder_LevelGuard::~BerEncoder_LevelGuard()
{
    --d_encoder->d_currentDepth;
}

                       // -----------------------------
                       // struct BerEncoder_encodeProxy
                       // -----------------------------

template <typename TYPE>
inline
int BerEncoder_encodeProxy::operator()(const TYPE&, bslmf::Nil)
{
    BSLS_ASSERT_SAFE(0);
    return -1;
}

template <typename TYPE, typename ANY_CATEGORY>
inline
int BerEncoder_encodeProxy::operator()(const TYPE&  object,
                                       ANY_CATEGORY category)
{
    return d_encoder->encodeImpl(object,
                                 d_tagClass,
                                 d_tagNumber,
                                 d_formattingMode,
                                 category);
}

template <typename TYPE>
inline
int BerEncoder_encodeProxy::operator()(const TYPE& object)
{
    typedef typename
    bdlat_TypeCategory::Select<TYPE>::Type TypeCategory;

    return this->operator()(object, TypeCategory());
}

                              // ----------------
                              // class BerEncoder
                              // ----------------

// ACCESSORS
inline
const BerEncoderOptions *BerEncoder::options() const
{
    return d_options;
}

inline
BerEncoder::ErrorSeverity BerEncoder::errorSeverity() const
{
    return d_severity;
}

inline
bslstl::StringRef BerEncoder::loggedMessages() const
{
    if (d_logStream) {
        return bslstl::StringRef(d_logStream->data(), d_logStream->length());
    }

    return bslstl::StringRef();
}

inline
bsl::ostream& BerEncoder::logStream()
{
    if (d_logStream == 0) {
        d_logStream = new(d_logArea.buffer()) MemOutStream(d_allocator);
    }
    return *d_logStream;
}

template <typename TYPE>
int BerEncoder::encode(bsl::streambuf *streamBuf, const TYPE& value)
{
    BSLS_ASSERT(!d_streamBuf);

    d_streamBuf = streamBuf;
    d_severity  = e_BER_SUCCESS;

    if (d_logStream != 0) {
        d_logStream->reset();
    }

    d_currentDepth = 0;

    int rc;

    if (! d_options) {
        BerEncoderOptions options;  // temporary options object
        d_options = &options;
        BerEncoder_UniversalElementVisitor visitor(
                                              this,
                                              bdlat_FormattingMode::e_DEFAULT);

        rc = visitor(value);
        d_options = 0;
    }
    else {
        BerEncoder_UniversalElementVisitor visitor(
                                              this,
                                              bdlat_FormattingMode::e_DEFAULT);
        rc = visitor(value);
    }

    d_streamBuf = 0;

    streamBuf->pubsync();

    return rc;
}

template <typename TYPE>
int BerEncoder::encode(bsl::ostream& stream, const TYPE& value)
{
    if (!stream.good()) {
        return -1;
    }

    if (0 != this->encode(stream.rdbuf(), value)) {
        stream.setstate(bsl::ios_base::failbit);
        return -1;
    }
    return 0;
}

// PRIVATE MANIPULATORS
template <typename TYPE>
int BerEncoder::encodeImpl(const TYPE&                value,
                           BerConstants::TagClass     tagClass,
                           int                        tagNumber,
                           int                        formattingMode,
                           bdlat_TypeCategory::Choice )
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    const BerConstants::TagType tagType = BerConstants::e_CONSTRUCTED;

    int rc = BerUtil::putIdentifierOctets(d_streamBuf,
                                          tagClass,
                                          tagType,
                                          tagNumber);
    if (rc | BerUtil::putIndefiniteLengthOctet(d_streamBuf)) {
        return k_FAILURE;                                             // RETURN
    }

    const bool isUntagged = formattingMode
                          & bdlat_FormattingMode::e_UNTAGGED;

    if (!isUntagged) {
        // According to X.694 (clause 20.4), an XML choice (not anonymous)
        // element is encoded as a sequence with 1 element.

        rc = BerUtil::putIdentifierOctets(d_streamBuf,
                                          BerConstants::e_CONTEXT_SPECIFIC,
                                          tagType,
                                          0);
        if (rc | BerUtil::putIndefiniteLengthOctet(d_streamBuf)) {
            return k_FAILURE;
        }
    }

    const int selectionId = bdlat_ChoiceFunctions::selectionId(value);

    if (bdlat_ChoiceFunctions::k_UNDEFINED_SELECTION_ID != selectionId) {

        BerEncoder_Visitor visitor(this);

        if (0 != bdlat_ChoiceFunctions::accessSelection(value, visitor)) {
            return k_FAILURE;                                         // RETURN
        }
    }
    else {

         if (d_options->disableUnselectedChoiceEncoding()) {

            this->logError(tagClass,
                           tagNumber);

            return k_FAILURE;                                         // RETURN
         }

    }

    if (!isUntagged) {
        // According to X.694 (clause 20.4), an XML choice (not anonymous)
        // element is encoded as a sequence with 1 element.

        // Don't waste time checking the result of this call -- the only thing
        // that can go wrong is eof, which will happen again when we call it
        // again below.
        BerUtil::putEndOfContentOctets(d_streamBuf);
    }

    return BerUtil::putEndOfContentOctets(d_streamBuf);
}

template <typename TYPE>
int BerEncoder::encodeImpl(const TYPE&                       value,
                           BerConstants::TagClass            tagClass,
                           int                               tagNumber,
                           int                               formattingMode,
                           bdlat_TypeCategory::NullableValue )
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    bool isNillable = formattingMode & bdlat_FormattingMode::e_NILLABLE;

    if (isNillable) {

        // nillable is encoded in BER as a sequence with one optional element

        int rc = BerUtil::putIdentifierOctets(d_streamBuf,
                                              tagClass,
                                              BerConstants::e_CONSTRUCTED,
                                              tagNumber);
        if (rc | BerUtil::putIndefiniteLengthOctet(d_streamBuf)) {
            return k_FAILURE;
        }

        if (!bdlat_NullableValueFunctions::isNull(value)) {

            BerEncoder_encodeProxy proxy1 = {
                                 this,
                                 BerConstants::e_CONTEXT_SPECIFIC, // tagClass
                                 0,                                // tagNumber
                                 formattingMode };

            if (0 != bdlat_NullableValueFunctions::accessValue(value,
                                                               proxy1)) {
                return k_FAILURE;
            }
        } // end of bdlat_NullableValueFunctions::isNull(...)

        return BerUtil::putEndOfContentOctets(d_streamBuf);
    } // end of isNillable

    if (!bdlat_NullableValueFunctions::isNull(value)) {

        BerEncoder_encodeProxy proxy2 = { this,
                                          tagClass,
                                          tagNumber,
                                          formattingMode };

        if (0 != bdlat_NullableValueFunctions::accessValue(value, proxy2)) {
                return k_FAILURE;
        }
    }

    return k_SUCCESS;
}

template <typename TYPE>
int BerEncoder::encodeImpl(const TYPE&                        value,
                           BerConstants::TagClass             tagClass,
                           int                                tagNumber,
                           int                                formattingMode,
                           bdlat_TypeCategory::CustomizedType )
{
    typedef typename
    bdlat_CustomizedTypeFunctions::BaseType<TYPE>::Type BaseType;

    typedef typename
    bdlat_TypeCategory::Select<BaseType>::Type          BaseTypeCategory;

    int rc = encodeImpl(
                      bdlat_CustomizedTypeFunctions::convertToBaseType(value),
                      tagClass,
                      tagNumber,
                      formattingMode,
                      BaseTypeCategory());

    return rc;
}

template <typename TYPE>
int BerEncoder::encodeImpl(const TYPE&                     value,
                           BerConstants::TagClass          tagClass,
                           int                             tagNumber,
                           int                             ,
                           bdlat_TypeCategory::Enumeration )
{
    int rc = BerUtil::putIdentifierOctets(d_streamBuf,
                                          tagClass,
                                          BerConstants::e_PRIMITIVE,
                                          tagNumber);

    int intValue;
    bdlat_EnumFunctions::toInt(&intValue, value);

    rc |= BerUtil::putValue(d_streamBuf, intValue);

    return rc;
}

template <typename TYPE>
int BerEncoder::encodeImpl(const TYPE&                  value,
                           BerConstants::TagClass       tagClass,
                           int                          tagNumber,
                           int                          ,
                           bdlat_TypeCategory::Sequence )
{
    BerEncoder_Visitor visitor(this);

    int rc = BerUtil::putIdentifierOctets(d_streamBuf,
                                          tagClass,
                                          BerConstants::e_CONSTRUCTED,
                                          tagNumber);
    rc |= BerUtil::putIndefiniteLengthOctet(d_streamBuf);
    if (rc) {
        return rc;
    }

    rc = bdlat_SequenceFunctions::accessAttributes(value, visitor);
    rc |= BerUtil::putEndOfContentOctets(d_streamBuf);

    return rc;
}

template <typename TYPE>
int BerEncoder::encodeImpl(const TYPE&                     value,
                                BerConstants::TagClass     tagClass,
                                int                        tagNumber,
                                int                        ,
                                bdlat_TypeCategory::Simple )
{
    int rc = BerUtil::putIdentifierOctets(d_streamBuf,
                                          tagClass,
                                          BerConstants::e_PRIMITIVE,
                                          tagNumber);
    rc |= BerUtil::putValue(d_streamBuf, value, d_options);

    return rc;
}

template <typename TYPE>
inline
int BerEncoder::encodeImpl(const TYPE&               value,
                           BerConstants::TagClass    tagClass,
                           int                       tagNumber,
                           int                       formattingMode,
                           bdlat_TypeCategory::Array )
{
    enum { k_SUCCESS = 0,  k_FAILURE = -1 };

    if (d_currentDepth <= 1 || tagClass == BerConstants::e_UNIVERSAL) {
        return k_FAILURE;
    }
    // Note: bsl::vector<char> is handled as a special case in the CPP file.
    return this->encodeArrayImpl(value,
                                 tagClass,
                                 tagNumber,
                                 formattingMode);
}

template <typename TYPE>
int
BerEncoder::encodeArrayImpl(const TYPE&             value,
                            BerConstants::TagClass  tagClass,
                            int                     tagNumber,
                            int                     formattingMode)
{
    enum { k_FAILURE = -1, k_SUCCESS = 0 };

    const int size = static_cast<int>(bdlat_ArrayFunctions::size(value));

    if (0 == size && d_options && !d_options->encodeEmptyArrays()) {
        return k_SUCCESS;                                             // RETURN
    }

    const BerConstants::TagType tagType = BerConstants::e_CONSTRUCTED;

    int rc = BerUtil::putIdentifierOctets(d_streamBuf,
                                          tagClass,
                                          tagType,
                                          tagNumber);
    rc |= BerUtil::putIndefiniteLengthOctet(d_streamBuf);
    if (rc) {
        return k_FAILURE;                                             // RETURN
    }

    BerEncoder_UniversalElementVisitor visitor(this, formattingMode);

    for (int i = 0; i < size; ++i) {
        if (0 != bdlat_ArrayFunctions::accessElement(value, visitor, i)) {

            this->logError(tagClass,
                           tagNumber,
                           0,  // bdlat_TypeName::name(value),
                           i);

            return k_FAILURE;                                         // RETURN
        }
    }

    return BerUtil::putEndOfContentOctets(d_streamBuf);
}

template <typename TYPE>
inline
int BerEncoder::encodeImpl(const TYPE&                          value,
                                BerConstants::TagClass          tagClass,
                                int                             tagNumber,
                                int                             formattingMode,
                                bdlat_TypeCategory::DynamicType )
{
    BerEncoder_encodeProxy proxy = { this,
                                     tagClass,
                                     tagNumber,
                                     formattingMode
                                   };

    return bdlat_TypeCategoryUtil::accessByCategory(value, proxy);
}

                      // --------------------------------
                      // private class BerEncoder_Visitor
                      // --------------------------------

// CREATORS
inline
BerEncoder_Visitor::BerEncoder_Visitor(BerEncoder *encoder)
: d_encoder(encoder)
, d_levelGuard(encoder)
{
}

inline
BerEncoder_Visitor::~BerEncoder_Visitor()
{
}

// MANIPULATORS
template <typename TYPE, typename INFO>
inline
int BerEncoder_Visitor::operator()(const TYPE& value, const INFO& info)
{
    typedef typename
    bdlat_TypeCategory::Select<TYPE>::Type TypeCategory;

    int rc = d_encoder->encodeImpl(value,
                                   BerConstants::e_CONTEXT_SPECIFIC,
                                   info.id(),
                                   info.formattingMode(),
                                   TypeCategory());

    if (rc) {
        d_encoder->logError(BerConstants::e_CONTEXT_SPECIFIC,
                            info.id(),
                            info.name());
    }

    return rc;
}

              // ------------------------------------------------
              // private class BerEncoder_UniversalElementVisitor
              // ------------------------------------------------

// CREATORS
inline
BerEncoder_UniversalElementVisitor::
BerEncoder_UniversalElementVisitor(BerEncoder *encoder,
                                   int         formattingMode)
: d_encoder(encoder)
, d_formattingMode(formattingMode)
, d_levelGuard(encoder)
{
}

inline
BerEncoder_UniversalElementVisitor::
~BerEncoder_UniversalElementVisitor()
{
}

// MANIPULATORS
template <typename TYPE>
int BerEncoder_UniversalElementVisitor::operator()(const TYPE& value)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    typedef typename
    bdlat_TypeCategory::Select<TYPE>::Type TypeCategory;

    BerUniversalTagNumber::Value tagNumber = BerUniversalTagNumber::select(
                                                         value,
                                                         d_formattingMode,
                                                         d_encoder->options());

    if (d_encoder->encodeImpl(value,
                              BerConstants::e_UNIVERSAL,
                              static_cast<int>(tagNumber),
                              d_formattingMode,
                              TypeCategory())) {
        d_encoder->logError(BerConstants::e_UNIVERSAL,
                            tagNumber);
        return k_FAILURE;
    }

    return k_SUCCESS;
}

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