// balber_berdecoder.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_BERDECODER
#define INCLUDED_BALBER_BERDECODER

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

//@PURPOSE: Provide a BER decoder class.
//
//@CLASSES:
//  balber::BerDecoder: BER decoder
//
//@SEE_ALSO: balber_berencoder, bdem_bdemdecoder, balxml_decoder
//
//@DESCRIPTION: This component defines a single class, 'balber::BerDecoder',
// that contains a parameterized 'decode' function.  The 'decode' function
// decodes data read from a specified stream and loads the corresponding object
// to an object of the parameterized type.  The 'decode' method is overloaded
// for two types of input streams:
//: o 'bsl::streambuf'
//: o 'bsl::istream'
//
// This class decodes objects based on the X.690 BER specification and is
// restricted to types supported by the 'bdlat' framework.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Decoding 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());
//..
// Next, 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());
//..
// Now, we create a 'bdlsb::FixedMemInStreamBuf' object to manage our access
// to the data portion of the 'bdlsb::MemOutStreamBuf' (where our BER encoding
// resides), decode the values found there, and use them to set the value
// of an 'usage::EmployeeRecord' object.
//..
//  balber::BerDecoderOptions  options;
//  balber::BerDecoder         decoder(&options);
//  bdlsb::FixedMemInStreamBuf isb(osb.data(), osb.length());
//  usage::EmployeeRecord      obj;
//
//  rc = decoder.decode(&isb, &obj);
//  assert(0 == rc);
//..
// Finally, we confirm that the object defined by the BER encoding has the
// same value as the original object.
//..
//  assert(bob.name()   == obj.name());
//  assert(bob.age()    == obj.age());
//  assert(bob.salary() == obj.salary());
//..

#include <balscm_version.h>

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

#include <bdlat_arrayfunctions.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_valuetypefunctions.h>

#include <bslma_allocator.h>

#include <bsl_string.h>

#include <bdlb_variant.h>

#include <bdlsb_memoutstreambuf.h>

#include <bsls_assert.h>
#include <bsls_objectbuffer.h>
#include <bsls_platform.h>
#include <bsls_review.h>

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

namespace BloombergLP {
namespace balber {

class BerDecoder_Node;
class BerDecoder_NodeVisitor;
class BerDecoder_UniversalElementVisitor;

                              // ================
                              // class BerDecoder
                              // ================

class BerDecoder {
    // This class contains the parameterized 'decode' functions that decode
    // data (in BER format) from an incoming stream into 'bdlat' types.

  private:
    // 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.

        bdlsb::MemOutStreamBuf d_sb;

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

      public:
        // CREATORS
        MemOutStream(bslma::Allocator *basicAllocator = 0);
            // Create a stream 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 the empty state.

        // ACCESSORS
        const char *data() const;
            // Return a pointer to the memory containing the formatted 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 BerDecoderOptions         *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 level
    bsl::streambuf                  *d_streamBuf;    // held, not owned
    int                              d_currentDepth; // current depth

    int                              d_numUnknownElementsSkipped;
                                                     // number of unknown
                                                     // elements skipped

    BerDecoder_Node                 *d_topNode;      // last node

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

    // FRIENDS
    friend class BerDecoder_Node;

  private:
    // PRIVATE MANIPULATORS
    ErrorSeverity logError(const char *msg);
        // Log the specified 'msg', upgrade the severity level, and return
        // 'e_BER_ERROR'.

    void logErrorImp(const char *msg);
        // Log the specified 'msg' and upgrade the severity level.

    ErrorSeverity logMsg(const char *prefix, const char *msg);
        // Log the specified 'prefix' and 'msg' and return 'errorSeverity()'.

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

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

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

    // MANIPULATORS
    template <typename TYPE>
    int decode(bsl::streambuf *streamBuf, TYPE *variable);
        // Decode an object of parameterized 'TYPE' from the specified
        // 'streamBuf' and load the result into the specified 'variable'.
        // Return 0 on success, and a non-zero value otherwise.

    template <typename TYPE>
    int decode(bsl::istream& stream, TYPE *variable);
        // Decode an object of parameterized 'TYPE' from the specified 'stream'
        // and load the result into the specified modifiable 'variable'.
        // Return 0 on success, and a non-zero value otherwise.  If the
        // decoding fails 'stream' will be invalidated.

    void setNumUnknownElementsSkipped(int value);
        // Set the number of unknown elements skipped by the decoder during the
        // current decoding operation to the specified 'value'.  The behavior
        // is undefined unless '0 <= value'.

    // ACCESSORS
    const BerDecoderOptions *decoderOptions() const;
        // Return the address of the BER decoder options.

    bool maxDepthExceeded() const;
       // Return 'true' if the maximum depth level is exceeded and 'false'
       // otherwise.

    int numUnknownElementsSkipped() const;
        // Return the number of unknown elements that were skipped during the
        // previous decoding operation.  Note that unknown elements are skipped
        // only if 'true == options()->skipUnknownElements()'.

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

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


                       // =============================
                       // private class BerDecoder_Node
                       // =============================

class BerDecoder_Node {
    // This class provides current context for BER decoding process and
    // represents a node for BER element.  The BER element consists of element
    // tag, length field, body field and optional end of tag.  The class also
    // provides various methods to read the different parts of BER element such
    // as tag header (tag itself and length fields), body for any type of data,
    // and optional tag trailer.

    // DATA
    BerDecoder             *d_decoder;              // decoder,
                                                    // held, not owned
    BerDecoder_Node        *d_parent;               // parent node,
                                                    // held, not owned
    BerConstants::TagClass  d_tagClass;             // tag class
    BerConstants::TagType   d_tagType;              // tag type
    int                     d_tagNumber;            // tag id or number
    int                     d_expectedLength;       // body length
    int                     d_consumedHeaderBytes;  // header bytes read
    int                     d_consumedBodyBytes;    // body bytes read
    int                     d_consumedTailBytes;    // trailer bytes read
    int                     d_formattingMode;       // formatting mode
    const char             *d_fieldName;            // name of the field

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

  private:
    // PRIVATE MANIPULATORS
    int decode(bsl::vector<char> *variable, bdlat_TypeCategory::Array);
    int decode(bsl::vector<unsigned char> *variable,
               bdlat_TypeCategory::Array);
    template <typename TYPE>
    int decode(TYPE *variable, bdlat_TypeCategory::Array);
    template <typename TYPE>
    int decode(TYPE *variable, bdlat_TypeCategory::Choice);
    template <typename TYPE>
    int decode(TYPE *variable, bdlat_TypeCategory::NullableValue);
    template <typename TYPE>
    int decode(TYPE *variable, bdlat_TypeCategory::CustomizedType);
    template <typename TYPE>
    int decode(TYPE *variable, bdlat_TypeCategory::Enumeration);
    template <typename TYPE>
    int decode(TYPE *variable, bdlat_TypeCategory::Sequence);
    template <typename TYPE>
    int decode(TYPE *variable, bdlat_TypeCategory::Simple);
    template <typename TYPE>
    int decode(TYPE *variable, bdlat_TypeCategory::DynamicType);
        // Family of methods to decode current element into the specified
        // 'variable' of category 'bdlat_TypeCategory'.  Return zero on
        // success, and a non-zero value otherwise. the tag header is already
        // read at the moment of call and input stream is positioned at the
        // first byte of the body field.

    template <typename TYPE>
    int decodeArray(TYPE *variable);
        // Decode the current element, an array, into specified 'variable'.
        // Return zero on success, and a non-zero value otherwise.

    template <typename TYPE>
    int decodeChoice(TYPE *variable);
        // Decode the current element, which is a choice object, into specified
        // 'variable'.  Return zero on success, and a non-zero value otherwise.

  public:
    // CREATORS
    BerDecoder_Node(BerDecoder *decoder);

    template <typename TYPE>
    BerDecoder_Node(BerDecoder *decoder, const TYPE *variable);

    ~BerDecoder_Node();

    // MANIPULATORS
    template <typename TYPE>
    int operator()(TYPE *object, bslmf::Nil);

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

    template <typename TYPE>
    int operator()(TYPE *object);

    void print(bsl::ostream&  out,
               int            depth,
               int            spacePerLevel = 0,
               const char    *prefixText = 0) const;
        // Print the content of node to the specified stream 'out'.  'depth' is
        // the value 'd_decoder->currentDepth' assumed after node was created.

    void printStack(bsl::ostream& out) const;
        // Print the chain of nodes to the specified 'out' stream, starting
        // from this node and iterating to the parent node, then its parent,
        // etc.

    void setFormattingMode(int formattingMode);
        // Set formatting mode specified by 'formattingMode'.

    void setFieldName(const char *name);
        // Set object field name associated with this node to the specified
        // 'name'.

    int logError(const char *msg);
        // Set the node severity to 'e_BER_ERROR', print the error message
        // specified by 'msg' to the decoder's log, print the stack of nodes to
        // the decoder's log, and return a non-zero value.

    int readTagHeader();
        // Read the node tag field containing tag class, tag type and tag
        // number, and the node length field.  Return zero on success, and a
        // non-zero value otherwise.

    int readTagTrailer();
        // Read the node end-of-octets field, if such exists, so the stream
        // will be positioned at the start of next node.  Return zero on
        // success and a non-zero value otherwise.

    bool hasMore();
        // Return 'true' if current node has more embedded elements and return
        // 'false' otherwise.

    int skipField();
        // Skip the field body.  The identifier octet and length have already
        // been extracted.  Return zero on success, and a non-zero value
        // otherwise.  Note that method must be called when input stream is
        // positioned at the first byte of the body field.

    int readVectorChar(bsl::vector<char> *variable);
        // Load the node body content into the specified 'variable'.  Return 0
        // on success, and a non-zero value otherwise.

    int readVectorUnsignedChar(bsl::vector<unsigned char> *variable);
        // Load the node body content into the specified 'variable'.  Return 0
        // on success, and a non-zero value otherwise.

    // ACCESSORS
    BerDecoder_Node *parent() const;
        // Return the address of the parent node.

    BerConstants::TagClass tagClass() const;
        // Return the BER tag class for this node.

    BerConstants::TagType tagType() const;
        // Return the BER tag type for this node.

    int tagNumber() const;
        // Return the BER tag number for this node.

    int formattingMode() const;
        // Return formatting mode for this node.

    const char *fieldName() const;
        // Return field name for this node.

    int length() const;
       // Return expected length of the body or -1 when the length is
       // indefinite.

    int startPos() const;
        // Return the position of node tag from the beginning of input stream.
};

                    // ====================================
                    // private class BerDecoder_NodeVisitor
                    // ====================================

class BerDecoder_NodeVisitor {
    // This class is used as a visitor for visiting contained objects during
    // decoding.

    // DATA
    BerDecoder_Node *d_node;  // current node, held, not owned

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

  public:
    // CREATORS
    BerDecoder_NodeVisitor(BerDecoder_Node *node);

    //! ~BerDecoder_NodeVisitor() = default;

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

              // ================================================
              // private class BerDecoder_UniversalElementVisitor
              // ================================================

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

    // DATA
    BerDecoder_Node d_node;  // a new node

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

  public:
    // CREATORS
    BerDecoder_UniversalElementVisitor(BerDecoder *d_decoder);

    //! ~BerDecoder_UniversalElementVisitor() = default;

    // MANIPULATORS
    template <typename TYPE>
    int operator()(TYPE *variable);
};

                          // =======================
                          // class BerDecoder_Zeroer
                          // =======================

class BerDecoder_Zeroer {
    // This class is a deleter that just zeroes out a given pointer upon
    // destruction, for making code exception-safe.

    // DATA
    const BerDecoderOptions **d_options_p;  // address of pointer to zero
                                            // out upon destruction

  public:
    // CREATORS
    BerDecoder_Zeroer(const BerDecoderOptions **options)
    : d_options_p(options)
    {
    }

    ~BerDecoder_Zeroer()
    {
        *d_options_p = 0;
    }
};

}  // close package namespace

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

                   // --------------------------------------
                   // class balber::BerDecoder::MemOutStream
                   // --------------------------------------

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

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

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

inline
int balber::BerDecoder::MemOutStream::length() const
{
    return (int)d_sb.length();
}

namespace balber {
                              // ----------------
                              // class BerDecoder
                              // ----------------

// MANIPULATORS
inline BerDecoder::ErrorSeverity
BerDecoder::logError(const char *msg)
{
    // This is inline just so compilers see it cannot return SUCCESS, thereby
    // improving flow analysis and choking off spurious warnings.

    logErrorImp(msg);
    return e_BER_ERROR;
}

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

template <typename TYPE>
inline
int BerDecoder::decode(bsl::istream& stream, TYPE *variable)
{
    if (!stream.good()) {
        return -1;
    }

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

    return 0;
}

template <typename TYPE>
int BerDecoder::decode(bsl::streambuf *streamBuf, TYPE *variable)
{
    BSLS_ASSERT(0 == d_streamBuf);

    d_streamBuf                 = streamBuf;
    d_currentDepth              = 0;
    d_severity                  = e_BER_SUCCESS;
    d_numUnknownElementsSkipped = 0;

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

    d_topNode = 0;

    bdlat_ValueTypeFunctions::reset(variable);

    int rc = d_severity;

    if (! d_options) {
        // Create temporary options object
        BerDecoderOptions                  options; d_options = &options;
        BerDecoder_Zeroer                  zeroer(&d_options);
        BerDecoder_UniversalElementVisitor visitor(this);
        rc = visitor(variable);
    }
    else {
        BerDecoder_UniversalElementVisitor visitor(this);
        rc = visitor(variable);
    }

    d_streamBuf = 0;
    return rc;
}

inline
void BerDecoder::setNumUnknownElementsSkipped(int value)
{
    BSLS_ASSERT(0 <= value);

    d_numUnknownElementsSkipped = value;
}

// ACCESSORS
inline
const BerDecoderOptions *BerDecoder::decoderOptions() const
{
    return d_options;
}

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

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

    return bslstl::StringRef();
}

inline
bool BerDecoder::maxDepthExceeded() const
{
    return d_currentDepth > d_options->maxDepth();
}

inline
int BerDecoder::numUnknownElementsSkipped() const
{
    return d_numUnknownElementsSkipped;
}

                       // -----------------------------
                       // private class BerDecoder_Node
                       // -----------------------------

// CREATORS
inline
BerDecoder_Node::BerDecoder_Node(BerDecoder *decoder)
: d_decoder            (decoder)
, d_parent             (d_decoder->d_topNode)
, d_tagClass           (BerConstants::e_UNIVERSAL)
, d_tagType            (BerConstants::e_PRIMITIVE)
, d_tagNumber          (0)
, d_expectedLength     (0)
, d_consumedHeaderBytes(0)
, d_consumedBodyBytes  (0)
, d_consumedTailBytes  (0)
, d_formattingMode     (bdlat_FormattingMode::e_DEFAULT)
, d_fieldName          (0)
{
    ++d_decoder->d_currentDepth;
    if (d_parent) {
        d_formattingMode = d_parent->d_formattingMode;
    }
    d_decoder->d_topNode = this;
}

inline
BerDecoder_Node::~BerDecoder_Node()
{
    if (d_parent) {
        d_parent->d_consumedBodyBytes += d_consumedHeaderBytes
                                      +  d_consumedBodyBytes
                                      +  d_consumedTailBytes;
    }

    d_decoder->d_topNode = d_parent;
    --d_decoder->d_currentDepth;
}

// MANIPULATORS
inline
bool
BerDecoder_Node::hasMore()
{
    BSLS_ASSERT(d_tagType == BerConstants::e_CONSTRUCTED);

    if (BerUtil::k_INDEFINITE_LENGTH == d_expectedLength) {
        return 0 != d_decoder->d_streamBuf->sgetc();
    }

    return d_expectedLength > d_consumedBodyBytes;
}

// ACCESSORS
inline
BerDecoder_Node*BerDecoder_Node::parent() const
{
    return d_parent;
}

inline
BerConstants::TagClass BerDecoder_Node::tagClass() const
{
    return d_tagClass;
}

inline
BerConstants::TagType BerDecoder_Node::tagType() const
{
    return d_tagType;
}

inline
int BerDecoder_Node::tagNumber() const
{
    return d_tagNumber;
}

inline
int BerDecoder_Node::formattingMode() const
{
    return d_formattingMode;
}

inline
const char *BerDecoder_Node::fieldName() const
{
    return d_fieldName;
}

inline
int BerDecoder_Node::length() const
{
    return d_expectedLength;
}

// MANIPULATORS
inline
void BerDecoder_Node::setFormattingMode(int formattingMode)
{
    d_formattingMode = formattingMode;
}

inline
void BerDecoder_Node::setFieldName(const char *name)
{
    d_fieldName = name;
}

template <typename TYPE>
inline
int BerDecoder_Node::operator()(TYPE *, bslmf::Nil)
{
    BSLS_ASSERT(0 && "Should never execute this function");

    return -1;
}

template <typename TYPE, typename ANY_CATEGORY>
inline
int BerDecoder_Node::operator()(TYPE *object, ANY_CATEGORY category)
{
    return this->decode(object, category);
}

template <typename TYPE>
inline
int BerDecoder_Node::operator()(TYPE *object)
{
    typedef typename bdlat_TypeCategory::Select<TYPE>::Type Tag;
    return this->decode(object, Tag());
}

// PRIVATE MANIPULATORS
template <typename TYPE>
int
BerDecoder_Node::decode(TYPE *variable, bdlat_TypeCategory::Choice)
{
    // A misunderstanding of X.694 (clause 20.4), an XML choice (not anonymous)
    // element is encoded as a sequence (outer) with 1 element (inner).
    // However, if the element is anonymous (i.e., untagged), then there is no
    // inner tag.  This behavior is kept for backward compatibility.

    if (d_tagType != BerConstants::e_CONSTRUCTED) {
        return logError("Expected CONSTRUCTED tag type for choice");
    }

    bool isUntagged = d_formattingMode & bdlat_FormattingMode::e_UNTAGGED;

    int rc = BerDecoder::e_BER_SUCCESS;

    if (!isUntagged) {

        // 'typename' will be taken from predecessor node.
        BerDecoder_Node innerNode(d_decoder);
        rc = innerNode.readTagHeader();
        if (rc != BerDecoder::e_BER_SUCCESS) {
            return rc;  // error message is already logged
        }

        if (innerNode.tagClass() != BerConstants::e_CONTEXT_SPECIFIC) {
            return innerNode.logError(
                               "Expected CONTEXT tag class for tagged choice");
        }

        if (innerNode.tagType() != BerConstants::e_CONSTRUCTED) {
            return innerNode.logError(
                            "Expected CONSTRUCTED tag type for tagged choice");
        }

        if (innerNode.tagNumber() != 0) {
            return innerNode.logError(
                               "Expected 0 as a tag number for tagged choice");
        }

        if (innerNode.hasMore()) {
            // if shouldContinue returns false, then there is no selection
            rc  = innerNode.decodeChoice(variable);
            if (rc != BerDecoder::e_BER_SUCCESS) {
                return rc;  // error message is already logged
            }
        }

        rc = innerNode.readTagTrailer();
    }
    else if (this->hasMore()) {

       // if shouldContinue returns false, then there is no selection
       rc = this->decodeChoice(variable);
    }

    return rc;
}

template <typename TYPE>
int
BerDecoder_Node::decode(TYPE *variable, bdlat_TypeCategory::NullableValue)
{
    int rc = BerDecoder::e_BER_SUCCESS;

    if (d_formattingMode & bdlat_FormattingMode::e_NILLABLE) {
        // nillable is encoded in BER as a sequence with one optional element

        if (d_tagType != BerConstants::e_CONSTRUCTED) {
            return logError("Expected CONSTRUCTED tag type for nullable");
        }

        if (hasMore()) {

            // If 'hasMore' returns false, then the nullable value is null.
            BerDecoder_Node innerNode(d_decoder);
            rc = innerNode.readTagHeader();
            if (rc != BerDecoder::e_BER_SUCCESS) {
                return rc;  // error message is already logged
            }

            if (innerNode.tagClass() != BerConstants::e_CONTEXT_SPECIFIC) {
                return innerNode.logError(
                              "Expected CONTEXT tag class for inner nillable");
            }

            if (innerNode.tagNumber() != 0) {
                return innerNode.logError(
                                "Expected 0 as tag number for inner nillable");
            }

            bdlat_NullableValueFunctions::makeValue(variable);

            rc = bdlat_NullableValueFunctions::manipulateValue(variable,
                                                               innerNode);
            if (rc != BerDecoder::e_BER_SUCCESS) {
                return rc;  // error message is already logged
            }

            rc = innerNode.readTagTrailer();

        }  // this->hasMore()
        else {
            bdlat_ValueTypeFunctions::reset(variable);
        }
    }
    else {  // not 'bdlat_FormattingMode::e_NILLABLE'
        bdlat_NullableValueFunctions::makeValue(variable);
        rc = bdlat_NullableValueFunctions::manipulateValue(variable, *this);
    }

    return rc;
}

template <typename TYPE>
int
BerDecoder_Node::decode(TYPE *variable, bdlat_TypeCategory::CustomizedType)
{
    typedef typename
    bdlat_CustomizedTypeFunctions::BaseType<TYPE>::Type BaseType;

#ifdef BSLS_PLATFORM_HAS_PRAGMA_GCC_DIAGNOSTIC
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuninitialized"
#ifndef BSLS_PLATFORM_CMP_CLANG
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#endif

    BaseType base;

    typedef typename bdlat_TypeCategory::Select<BaseType>::Type BaseTag;
    int rc = this->decode(&base, BaseTag());

    if (rc != BerDecoder::e_BER_SUCCESS) {
        return rc;  // error message is already logged
    }

    if (bdlat_CustomizedTypeFunctions::convertFromBaseType(variable,
                                                           base) != 0) {
        return logError("Error converting from base type for customized");
    }

#ifdef BSLS_PLATFORM_HAS_PRAGMA_GCC_DIAGNOSTIC
#pragma GCC diagnostic pop
#endif

    return BerDecoder::e_BER_SUCCESS;
}

template <typename TYPE>
int
BerDecoder_Node::decode(TYPE *variable, bdlat_TypeCategory::Enumeration)
{
    int value = 0;
    int rc    = this->decode(&value, bdlat_TypeCategory::Simple());

    if (rc != BerDecoder::e_BER_SUCCESS) {
        return rc;  // error message is already logged
    }

    if (0 != bdlat_EnumFunctions::fromInt(variable, value)) {
        return logError("Error converting enumeration value");
    }

    return BerDecoder::e_BER_SUCCESS;
}

template <typename TYPE>
inline
int BerDecoder_Node::decode(TYPE *variable, bdlat_TypeCategory::Simple)
{
    if (d_tagType != BerConstants::e_PRIMITIVE) {
        return logError("Expected PRIMITIVE tag type for simple type");
    }

    if (BerUtil::getValue(d_decoder->d_streamBuf,
                          variable,
                          d_expectedLength,
                          *d_decoder->d_options) != 0) {
        return logError("Error reading value for simple type");
    }

    d_consumedBodyBytes = d_expectedLength;

    return BerDecoder::e_BER_SUCCESS;
}

template <typename TYPE>
int
BerDecoder_Node::decode(TYPE *variable, bdlat_TypeCategory::Sequence)
{
    if (d_tagType != BerConstants::e_CONSTRUCTED) {
        return logError("Expected CONSTRUCTED tag type for sequence");
    }

    while (this->hasMore()) {

        BerDecoder_Node innerNode(d_decoder);

        int rc = innerNode.readTagHeader();
        if (rc != BerDecoder::e_BER_SUCCESS) {
            return rc;  // error message is already logged
        }

        if (innerNode.tagClass() != BerConstants::e_CONTEXT_SPECIFIC) {
            return innerNode.logError(
                                 "Expected CONTEXT tag class inside sequence");
        }

        if (bdlat_SequenceFunctions::hasAttribute(*variable,
                                                  innerNode.tagNumber())) {

            BerDecoder_NodeVisitor visitor(&innerNode);

            rc = bdlat_SequenceFunctions::manipulateAttribute(
                                                        variable,
                                                        visitor,
                                                        innerNode.tagNumber());
        }
        else {
            rc = innerNode.skipField();
            d_decoder->setNumUnknownElementsSkipped(
                                   d_decoder->numUnknownElementsSkipped() + 1);
        }

        if (rc != BerDecoder::e_BER_SUCCESS) {
            return rc;  // error message is already logged
        }

        rc = innerNode.readTagTrailer();
        if (rc != BerDecoder::e_BER_SUCCESS) {
            return rc;  // error message is already logged
        }
    }

    return BerDecoder::e_BER_SUCCESS;
}

template <typename TYPE>
inline
int BerDecoder_Node::decode(TYPE *variable, bdlat_TypeCategory::Array)
{
    // Note: 'bsl::vector<char>' and 'bsl::vector<unsigned char>' are
    // handled as special cases in the CPP file.

    return this->decodeArray(variable);
}

template <typename TYPE>
inline
int BerDecoder_Node::decode(TYPE *variable, bdlat_TypeCategory::DynamicType)
{
    return bdlat_TypeCategoryUtil::manipulateByCategory(variable, *this);
}

template <typename TYPE>
int BerDecoder_Node::decodeChoice(TYPE *variable)
{
    BerDecoder_Node innerNode(d_decoder);

    int rc = innerNode.readTagHeader();
    if (rc != BerDecoder::e_BER_SUCCESS) {
        return rc;  // error message is already logged
    }

    if (innerNode.tagClass() != BerConstants::e_CONTEXT_SPECIFIC) {
        return innerNode.logError(
                             "Expected CONTEXT tag class for internal choice");
    }

    if (bdlat_ChoiceFunctions::hasSelection(*variable,
                                            innerNode.tagNumber())) {

        if (0 != bdlat_ChoiceFunctions::makeSelection(variable,
                                                      innerNode.tagNumber())) {

            return innerNode.logError("Unable to make choice selection");
        }

        BerDecoder_NodeVisitor visitor(&innerNode);

        rc = bdlat_ChoiceFunctions::manipulateSelection(variable, visitor);
    }
    else {
        rc = innerNode.skipField();
        d_decoder->setNumUnknownElementsSkipped(
                                   d_decoder->numUnknownElementsSkipped() + 1);
    }

    if (rc != BerDecoder::e_BER_SUCCESS) {
        return rc;  // error message is already logged
    }

    return innerNode.readTagTrailer();
}

template <typename TYPE>
int
BerDecoder_Node::decodeArray(TYPE *variable)
{
    if (d_tagType != BerConstants::e_CONSTRUCTED) {
        return logError("Expected CONSTRUCTED tag class for array");
    }

    const int maxSize = d_decoder->decoderOptions()->maxSequenceSize();

    int i = static_cast<int>(bdlat_ArrayFunctions::size(*variable));
    while (this->hasMore()) {
        int j = i + 1;

        if (j > maxSize) {
            return logError("Array size exceeds the limit");
        }

        bdlat_ArrayFunctions::resize(variable, j);

        BerDecoder_UniversalElementVisitor visitor(d_decoder);
        int rc = bdlat_ArrayFunctions::manipulateElement(variable, visitor, i);
        if (rc != BerDecoder::e_BER_SUCCESS) {
            return logError("Error in decoding array element");
        }
        i = j;
    }

    return BerDecoder::e_BER_SUCCESS;
}

                    // ------------------------------------
                    // private class BerDecoder_NodeVisitor
                    // ------------------------------------

// CREATORS
inline
BerDecoder_NodeVisitor::
BerDecoder_NodeVisitor(BerDecoder_Node *node)
: d_node(node)
{
}

// MANIPULATORS
template <typename TYPE, typename INFO>
inline
int BerDecoder_NodeVisitor::operator()(TYPE *variable, const INFO& info)
{
    d_node->setFormattingMode(info.formattingMode());
    d_node->setFieldName(info.name());

    return d_node->operator()(variable);
}

              // ------------------------------------------------
              // private class BerDecoder_UniversalElementVisitor
              // ------------------------------------------------

// CREATORS
inline
BerDecoder_UniversalElementVisitor::
BerDecoder_UniversalElementVisitor(BerDecoder *decoder)
: d_node(decoder)
{
}

// MANIPULATORS
template <typename TYPE>
int BerDecoder_UniversalElementVisitor::operator()(TYPE *variable)
{
    int                          alternateTag      = -1;
    BerUniversalTagNumber::Value expectedTagNumber =
                         BerUniversalTagNumber::select(*variable,
                                                       d_node.formattingMode(),
                                                       &alternateTag);

    int rc = d_node.readTagHeader();
    if (rc != BerDecoder::e_BER_SUCCESS) {
        return rc;  // error message is already logged
    }

    if (d_node.tagClass() != BerConstants::e_UNIVERSAL) {
        return d_node.logError("Expected UNIVERSAL tag class");
    }

    if (d_node.tagNumber() != static_cast<int>(expectedTagNumber)) {
        if (-1 == alternateTag || d_node.tagNumber() != alternateTag) {
            return d_node.logError("Unexpected tag number");
        }
    }

    rc = d_node(variable);

    if (rc != BerDecoder::e_BER_SUCCESS) {
        return rc;
    }

    rc = d_node.readTagTrailer();

    return rc;
}

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