// bdld_manageddatum.h                                                -*-C++-*-
#ifndef INCLUDED_BDLD_MANAGEDDATUM
#define INCLUDED_BDLD_MANAGEDDATUM

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

//@PURPOSE: Provide a smart-pointer-like manager for a 'Datum' object.
//
//@CLASSES:
//  bdld::ManagedDatum: a smart-pointer-like manager for a 'Datum' object
//
//@SEE_ALSO: bdld_datum
//
//@DESCRIPTION: This component implements a type, 'bdld::ManagedDatum', that
// provides two important services for 'Datum' objects:
//
//: 1 'ManagedDatum' provides value-semantic-like operations for 'Datum'.
//:
//: 2 'ManagedDatum' is a resource manager, similar to a smart pointer, for
//:   'Datum'.
//
// These services allow clients to use a 'ManagedDatum' object in most contexts
// where an object of a value-semantic type can be used (passed by value,
// stored in containers, and so on), even though 'ManagedDatum' is not strictly
// value-semantic.  These services are explored in subsequent sections.
//
// The 'Datum' type maintained by a 'ManagedDatum' provides a space-efficient
// discriminated union (i.e., a variant) holding the value of a scalar type
// (e.g., 'int', 'double', 'string') or an aggregate of other 'Datum' objects.
// See {'bdld_datum'} for more details.
//
///Value Semantics
///---------------
// 'ManagedDatum', while not strictly a value-semantic type, provides the full
// set of value-semantic-like operations for 'Datum' (see
// {'bsldoc_glossary'|Value-Semantic Operations}):
//
//: o Equality and Non-Equality Comparisons
//: o Copy Construction
//: o Copy Assignment
//: o Default Construction
//: o 'ostream' Printing
//
// In other words, the syntax of 'ManagedDatum' is *regular*, but not all of
// its copy behavior is value-semantic.  Specifically, for User Defined Types
// (i.e., those that 'bdld::Datum::clone' does not deep-copy) 'ManagedDatum'
// performs a shallow copy (copying the reference rather than the value), which
// is inconsistent with value-semantics.  For *all* other types 'ManagedDatum'
// copy operations (copy construction, copy assignment, and non-member 'swap'
// when the allocators differ) will deep-copy the value using 'Datum::clone,
// which creates a completely independent copy, with independent lifetime, by
// duplicating all data, even referenced data (except for UDTs).
//
// Note that a default constructed 'ManagedDatum', or a 'ManagedDatum' on which
// 'release' has been called, will have the null 'Datum' value.
//
///Resource Management
///-------------------
// A 'Datum' object's relationship to memory can be seen as analogous to a raw
// pointer, requiring calls to static functions 'Datum::create*' and
// 'Datum::destroy' to initialize and release resources (see the {'bdld_datum'}
// component documentation).  A 'ManagedDatum', by extension, provides a
// resource manager for a 'Datum' that is analogous to a smart pointer.
//
// The 'adopt' method of a 'ManagedDatum' is used to take ownership of a
// supplied 'Datum' object, after which point the 'ManagedDatum' object's
// destructor will free the resources of the managed 'Datum' (unless 'release'
// is subsequently called).  Similar to a smart pointer, a 'ManagedDatum'
// provides dereference operators to access the 'Datum' object under
// management.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Basic Use of 'bdld::ManagedDatum'
/// - - - - - - - - - - - - - - - - - - - - - -
// This example demonstrates the basic construction and manipulation of a
// 'ManagedDatum' object.
//
// First, we create a 'ManagedDatum' object that manages a 'Datum' holding a
// 'double' and verify that the managed object has the expected type and value:
//..
//  bslma::TestAllocator ta("test", veryVeryVerbose);
//
//  const ManagedDatum realObj(Datum::createDouble(-3.4375), &ta);
//
//  assert(realObj->isDouble());
//  assert(-3.4375 == realObj->theDouble());
//..
// Next, we create a 'ManagedDatum' object that holds a string and again verify
// that it has the expected type and value:
//..
//  const char         *str = "This is a string";
//  const ManagedDatum  strObj(Datum::copyString(str, &ta), &ta);
//
//  assert(strObj->isString());
//  assert(str == strObj->theString());
//..
// Then, we assign this 'ManagedDatum' object to another object and verify both
// objects have the same value:
//..
//  ManagedDatum strObj1(&ta);
//  strObj1 = strObj;
//  assert(strObj == strObj1);
//..
// Next, copy-construct this 'ManagedDatum' object and verify that the copy has
// the same value as the original:
//..
//  const ManagedDatum strObj2(strObj, &ta);
//  assert(strObj == strObj2);
//..
// Then, we create a 'ManagedDatum' object that holds an opaque pointer to a
// 'bdlt::Date' object and verify that the managed 'Date' has the expected
// value:
//..
//  bdlt::Date   udt;
//  ManagedDatum udtObj(Datum::createUdt(&udt, UDT_TYPE), &ta);
//
//  assert(udtObj->isUdt());
//  assert(&udt == udtObj->theUdt().data());
//  assert(UDT_TYPE == udtObj->theUdt().type());
//..
// Next, we assign a boolean value to this 'ManagedDatum' object and verify
// that it has the new type and value:
//..
//  udtObj.adopt(Datum::createBoolean(true));
//  assert(udtObj->isBoolean());
//  assert(true == udtObj->theBoolean());
//..
// Then, we create a 'ManagedDatum' object having an array and verify that it
// has the same array value.  Note that in practice we would use
// {'bdld_datumarraybuilder'}, but do not do so here for dependency reasons:
//..
//  const Datum datumArray[2] = {
//      Datum::createInteger(12),
//      Datum::copyString("A long string", &ta)
//  };
//
//  DatumMutableArrayRef arr;
//  Datum::createUninitializedArray(&arr, 2, &ta);
//  for (int i = 0; i < 2; ++i) {
//      arr.data()[i] = datumArray[i];
//  }
//  *(arr.length()) = 2;
//  const ManagedDatum arrayObj(Datum::adoptArray(arr), &ta);
//
//  assert(arrayObj->isArray());
//  assert(DatumArrayRef(datumArray, 2) == arrayObj->theArray());
//..
// Next, we create a 'ManagedDatum' object having a map and verify that it has
// the same map value.  Note that in practice we would use
// {'bdld_datummapbuilder'}, but do not do so here to for dependency reasons.
//..
//  const DatumMapEntry datumMap[2] = {
//      DatumMapEntry(StringRef("first", static_cast<int>(strlen("first"))),
//                    Datum::createInteger(12)),
//      DatumMapEntry(StringRef("second", static_cast<int>(strlen("second"))),
//                    Datum::copyString("A very long string", &ta))
//  };
//
//  DatumMutableMapRef mp;
//  Datum::createUninitializedMap(&mp, 2, &ta);
//  for (int i = 0; i < 2; ++i) {
//      mp.data()[i] = datumMap[i];
//  }
//  *(mp.size()) = 2;
//  const ManagedDatum mapObj(Datum::adoptMap(mp), &ta);
//
//  assert(mapObj->isMap());
//  assert(DatumMapRef(datumMap, 2, false, false) == mapObj->theMap());
//..
// Then, we create a 'Datum' object and assign its ownership to a
// 'ManagedDatum' object and verify that the ownership was transferred:
//..
//  const Datum  rcObj = Datum::copyString("This is a string", &ta);
//  ManagedDatum obj(Datum::createInteger(1), &ta);
//  obj.adopt(rcObj);
//  assert(obj.datum() == rcObj);
//..
// Next, we release the 'Datum' object managed by 'obj' and verify that it was
// released:
//..
//  const Datum internalObj = obj.release();
//  assert(obj->isNull());
//  assert(internalObj == rcObj);
//..
// Finally, we destroy the released 'Datum' object:
//..
//  Datum::destroy(internalObj, obj.get_allocator().mechanism());
//..

#include <bdlscm_version.h>

#include <bdld_datum.h>

#include <bslma_allocator.h>
#include <bslma_stdallocator.h>
#include <bslma_usesbslmaallocator.h>

#include <bslmf_isbitwisemoveable.h>
#include <bslmf_nestedtraitdeclaration.h>

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

#include <bsl_algorithm.h>
#include <bsl_iosfwd.h>

namespace BloombergLP {
namespace bdld {

                            // ==================
                            // class ManagedDatum
                            // ==================

class ManagedDatum {
    // This class implements a smart-pointer-like resource manager for a
    // 'Datum' object.

  private:
    // DATA
    Datum                d_data;       // storage for data
    bsl::allocator<char> d_allocator;  // allocator of dynamic memory

  public:
    // TYPES
    typedef bsl::allocator<char> allocator_type;

    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(ManagedDatum, bslma::UsesBslmaAllocator);
    BSLMF_NESTED_TRAIT_DECLARATION(ManagedDatum, bslmf::IsBitwiseMoveable);
        // 'ManagedDatum' objects are allocator-aware and bitwise movable.

    // CREATORS
    ManagedDatum();
    explicit ManagedDatum(const allocator_type& allocator);
        // Create a 'ManagedDatum' object having the default (null) value.
        // Optionally specify an 'allocator' (e.g., the address of a
        // 'bslma::Allocator' object) to supply memory; otherwise, the default
        // allocator is used.  Calling 'isNull' on the resulting managed
        // 'Datum' object returns 'true'.

    explicit ManagedDatum(const Datum&          datum,
                          const allocator_type& allocator = allocator_type());
        // Create a 'ManagedDatum' object that assumes ownership of the
        // specified 'datum'.  Optionally specify an 'allocator' (e.g., the
        // address of a 'bslma::Allocator' object) to supply memory; otherwise,
        // the default allocator is used.  The behavior is undefined unless
        // 'datum' was allocated using the indicated allocator and is not
        // subsequently destroyed externally using 'Datum::destroy'.

    ManagedDatum(const ManagedDatum&   original,
                 const allocator_type& allocator = allocator_type());
        // Create a 'ManagedDatum' object having the same value as the
        // specified 'original' object.  Optionally specify an 'allocator'
        // (e.g., the address of a 'bslma::Allocator' object) used to supply
        // memory; otherwise, the default allocator is used.  This operation
        // performs a 'clone' of the underlying 'Datum', see {Value Semantics}
        // for more detail.

    ~ManagedDatum();
        // Destroy this object and release all dynamically allocated memory
        // managed by this object.

    // MANIPULATORS
    ManagedDatum& operator=(const ManagedDatum& rhs);
        // Assign to this object the value of the specified 'rhs' object, and
        // return a non-'const' reference to this object.  This operation
        // performs a 'clone' of the underlying 'Datum', see {Value Semantics}
        // for more detail.

    void adopt(const Datum& obj);
        // Take ownership of the specified 'obj' and destroy the 'Datum'
        // object previously managed by this object.  The behavior is undefined
        // unless 'obj' was allocated using the same allocator used by this
        // object and is not subsequently destroyed externally using
        // 'Datum::destroy'.

    void clone(const Datum& value);
        // Assign to this object the specified 'value' by making a "deep copy"
        // of 'value', so that any dynamically allocated memory managed by
        // 'value' is cloned and not shared with 'value'.

    void makeNull();
        // Make the 'Datum' object managed by this object null and release all
        // dynamically allocated memory managed by this object.

    Datum release();
        // Return, *by* *value*, the 'Datum' object managed by this object and
        // set the managed object to null.  Ownership of the previously managed
        // 'Datum' object is transferred to the caller.

    void swap(ManagedDatum& other);
        // Efficiently exchange the value of this object with the value of the
        // specified 'other' object.  This method provides the no-throw
        // exception-safety guarantee.  The behavior is undefined unless this
        // object was created with the same allocator as 'other'.

    // ACCESSORS
    const Datum *operator->() const;
        // Return an address providing non-modifiable access to the 'Datum'
        // object managed by this object.

    const Datum& operator*() const;
        // Return a 'const' reference to the 'Datum' object managed by this
        // object.

    const Datum& datum() const;
        // Return a 'const' reference to the 'Datum' object managed by this
        // object.

                                  // Aspects

    bslma::Allocator *allocator() const;
        // !DEPRECATED!: Use 'get_allocator()' instead.
        //
        // Return 'get_allocator().mechanism()'.

    allocator_type get_allocator() const;
        // Return the allocator used by this object to supply memory.  Note
        // that if no allocator was supplied at construction the default
        // allocator in effect at construction is used.

    bsl::ostream& print(bsl::ostream& stream,
                        int           level          = 0,
                        int           spacesPerLevel = 4) const;
        // Format this object to the specified output 'stream' at the (absolute
        // value of) the optionally specified indentation 'level' and return a
        // reference to the modifiable 'stream'.  If 'level' is specified,
        // optionally specify 'spacesPerLevel', the number of spaces per
        // indentation level for this and all of its nested objects.  If
        // 'level' is negative, suppress indentation of the first line.  If
        // 'spacesPerLevel' is negative, format the entire output on one line,
        // suppressing all but the initial indentation (as governed by
        // 'level').  If 'stream' is not valid on entry, this operation has no
        // effect.
};

// FREE OPERATORS
bool operator==(const ManagedDatum& lhs, const ManagedDatum& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' 'ManagedDatum' objects
    // have the same value, and 'false' otherwise.  Two 'ManagedDatum' objects
    // have the same value if their corresponding managed 'Datum' objects have
    // the same value.  See the function-level documentation of the 'Datum'
    // equality-comparison operators for details.

bool operator!=(const ManagedDatum& lhs, const ManagedDatum& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' 'ManagedDatum' objects do
    // not have the same value, and 'false' otherwise.  Two 'ManagedDatum'
    // objects do not have the same value if their corresponding managed
    // 'Datum' objects do not have the same value.  See the function-level
    // documentation of the 'Datum' equality-comparison operators for details.

bsl::ostream& operator<<(bsl::ostream& stream, const ManagedDatum& rhs);
    // Write the specified 'rhs' value to the specified output 'stream' and
    // return a reference to the modifiable 'stream'.  This function has no
    // effect if 'stream' is not valid on entry.  Note that this method invokes
    // 'operator<<' defined for 'Datum'.  See the function-level documentation
    // of the 'operator<<' defined for 'Datum' for details of the format of the
    // output.

// FREE FUNCTIONS
void swap(ManagedDatum& a, ManagedDatum& b);
    // Exchange the values of the specified 'a' and 'b' objects.  This function
    // provides the no-throw exception-safety guarantee if the two objects were
    // created with the same allocator and the basic guarantee otherwise.  Note
    // that in case the allocators are different this function places a *clone*
    // of 'a' into 'b', and vice versa.  See {Value Semantics} on details of
    // the cloning that may happen.

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

                            // ------------------
                            // class ManagedDatum
                            // ------------------

// CREATORS
inline
ManagedDatum::ManagedDatum()
: d_data(Datum::createNull())
, d_allocator()
{
}

inline
ManagedDatum::ManagedDatum(const allocator_type& allocator)
: d_data(Datum::createNull())
, d_allocator(allocator)
{
}

inline
ManagedDatum::ManagedDatum(const Datum& datum, const allocator_type& allocator)
: d_data(datum)
, d_allocator(allocator)
{
}

inline
ManagedDatum::ManagedDatum(const ManagedDatum&   original,
                           const allocator_type& allocator)
: d_allocator(allocator)
{
    d_data = original.d_data.clone(d_allocator.mechanism());
}

inline
ManagedDatum::~ManagedDatum()
{
    Datum::destroy(d_data, d_allocator.mechanism());
}

// MANIPULATORS
inline
ManagedDatum& ManagedDatum::operator=(const ManagedDatum& rhs)
{
    ManagedDatum copy(rhs, d_allocator);
    swap(copy);
    return *this;
}

inline
void ManagedDatum::adopt(const Datum& obj)
{
    if (&obj != &d_data) {
        ManagedDatum(obj, d_allocator).swap(*this);
    }
}

inline
void ManagedDatum::clone(const Datum& value)
{
    Datum data = value.clone(d_allocator.mechanism());
    ManagedDatum(data, d_allocator).swap(*this);
}

inline
void ManagedDatum::makeNull()
{
    ManagedDatum(d_allocator).swap(*this);
}

inline
Datum ManagedDatum::release()
{
    Datum temp = d_data;
    d_data = Datum::createNull();
    return temp;
}

inline
void ManagedDatum::swap(ManagedDatum& other)
{
    BSLS_ASSERT(d_allocator == other.get_allocator());

    using bsl::swap;
    swap(d_data, other.d_data);
}

// ACCESSORS
inline
const Datum *ManagedDatum::operator->() const
{
    return &d_data;
}

inline
const Datum& ManagedDatum::operator*() const
{
    return d_data;
}

inline
const Datum& ManagedDatum::datum() const
{
    return d_data;
}

                                  // Aspects

inline
bslma::Allocator *ManagedDatum::allocator() const
{
    return get_allocator().mechanism();
}

inline
ManagedDatum::allocator_type ManagedDatum::get_allocator() const
{
    return d_allocator;
}

inline
bsl::ostream& ManagedDatum::print(bsl::ostream& stream,
                                  int           level,
                                  int           spacesPerLevel) const
{
    return d_data.print(stream, level, spacesPerLevel);
}

}  // close package namespace

// FREE OPERATORS
inline
bool bdld::operator==(const ManagedDatum& lhs, const ManagedDatum& rhs)
{
    return (lhs.datum() == rhs.datum());
}

inline
bool bdld::operator!=(const ManagedDatum& lhs, const ManagedDatum& rhs)
{
    return (lhs.datum() != rhs.datum());
}

inline
bsl::ostream& bdld::operator<<(bsl::ostream& stream, const ManagedDatum& rhs)
{
    return (stream << rhs.datum());
}

// FREE FUNCTIONS
inline
void bdld::swap(ManagedDatum& a, ManagedDatum& b)
{
    if (a.get_allocator() == b.get_allocator()) {
        a.swap(b);
    }
    else {
        ManagedDatum tempA(a, b.get_allocator());
        ManagedDatum tempB(b, a.get_allocator());

        a.swap(tempB);
        b.swap(tempA);
    }
}

}  // close enterprise namespace

#endif

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