// bdlb_nullableallocatedvalue.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_BDLB_NULLABLEALLOCATEDVALUE
#define INCLUDED_BDLB_NULLABLEALLOCATEDVALUE

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

//@PURPOSE: Provide a template for nullable allocated (out-of-place) objects.
//
//@CLASSES:
//  bdlb::NullableAllocatedValue: template for nullable allocated objects
//
//@SEE_ALSO: bdlb_nullablevalue
//
//@DESCRIPTION: This component provides a template class,
// 'bdlb::NullableAllocatedValue<TYPE>', that has nearly the same interface as
// 'bdlb::NullableValue' (see 'bdlb_nullablevalue'), but, in contrast with that
// template class, the implementation of 'bdlb::NullableAllocatedValue' does
// not require that the 'TYPE' parameter be a complete type when the *class* is
// instantiated.  However, the template parameter 'TYPE' must be complete when
// *methods* of the class (and free operators) are instantiated.
//
// Note that, as a consequence, the object of template parameter 'TYPE' that
// is managed by a 'bdlb::NullableAllocatedValue<TYPE>' object is necessarily
// allocated out-of-place.  This implies that an allocator is in effect for
// *any* 'bdlb::NullableAllocatedValue<TYPE>' object, regardless of the 'TYPE'.
// In particular, a 'bdlb::NullableAllocatedValue<int>' object, for example,
// incurs use of the default allocator (in effect at the time of creation of
// the object) unless an alternative allocator is supplied at construction.
//
///Usage
///-----
// The following snippets of code illustrate use of this component.
//
// Suppose we want to create a linked list of nodes that contain integers:
//..
//  struct LinkedListNode {
//      int                                          d_value;
//      bdlb::NullableAllocatedValue<LinkedListNode> d_next;
//  };
//..
// Note that 'bdlb::NullableValue<LinkedListNode>' cannot be used for 'd_next'
// because 'bdlb::NullableValue' requires that the template parameter 'TYPE' be
// a complete type when the class is instantiated.
//
// We can now traverse a linked list and add a new value at the end using the
// following code:
//..
//  void addValueAtEnd(LinkedListNode *node, int value)
//  {
//      while (!node->d_next.isNull()) {
//          node = &node->d_next.value();
//      }
//
//      node->d_next.makeValue();
//      node = &node->d_next.value();
//      node->d_value = value;
//  }
//..

#include <bdlscm_version.h>

#include <bdlb_printmethods.h>

#include <bslalg_scalarprimitives.h>
#include <bslalg_swaputil.h>

#include <bslma_allocator.h>
#include <bslma_deallocatorproctor.h>
#include <bslma_default.h>

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

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

#include <bslx_instreamfunctions.h>
#include <bslx_outstreamfunctions.h>
#include <bslx_versionfunctions.h>

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

#ifndef BDE_DONT_ALLOW_TRANSITIVE_INCLUDES
#include <bslalg_typetraits.h>
#endif // BDE_DONT_ALLOW_TRANSITIVE_INCLUDES

namespace BloombergLP {
namespace bdlb {

                  // ==================================
                  // class NullableAllocatedValue<TYPE>
                  // ==================================

template <class TYPE>
class NullableAllocatedValue {
    // This template class extends the set of values of its value-semantic
    // 'TYPE' parameter to include the notion of a "null" value.  If 'TYPE' is
    // fully value-semantic, then the augmented type
    // 'NullableAllocatedValue<TYPE>' will be as well.  In addition to
    // supporting all homogeneous value-semantic operations, conversions
    // between comparable underlying value types is also supported.  Two
    // nullable objects with different underlying types compare equal if their
    // underlying types are comparable and either (1) both objects are null or
    // (2) the non-null values compare equal.  Attempts to copy construct, copy
    // assign, or compare incompatible values types will fail to compile.  The
    // 'NullableAllocatedValue' template can be instantiated on an incomplete
    // type, but it cannot be instantiated on a type that overloads
    // 'operator&'.

    // DATA
    TYPE             *d_value_p;      // managed out-of-place 'TYPE' object
    bslma::Allocator *d_allocator_p;  // held, not owned

  public:
    // TYPES
    typedef TYPE ValueType;
        // 'ValueType' is an alias for the underlying 'TYPE' upon which this
        // template class is instantiated, and represents the type of the
        // managed object.

    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(NullableAllocatedValue,
                                   bslma::UsesBslmaAllocator);
    BSLMF_NESTED_TRAIT_DECLARATION(NullableAllocatedValue,
                                   bslmf::IsBitwiseMoveable);
    BSLMF_NESTED_TRAIT_DECLARATION(NullableAllocatedValue,
                                   bdlb::HasPrintMethod);

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

    NullableAllocatedValue(const NullableAllocatedValue&  original,
                           bslma::Allocator              *basicAllocator = 0);
        // Create a nullable object having the value of the specified
        // 'original' object.  Optionally specify a 'basicAllocator' used to
        // supply memory.  If 'basicAllocator' is 0, the currently installed
        // default allocator is used.

    NullableAllocatedValue(const TYPE&       value,
                           bslma::Allocator *basicAllocator = 0);
        // Create a nullable object having the specified 'value'.  Optionally
        // specify a 'basicAllocator' used to supply memory.  If
        // 'basicAllocator' is 0, the currently installed default allocator is
        // used.

    ~NullableAllocatedValue();
        // Destroy this object.

    // MANIPULATORS
    NullableAllocatedValue<TYPE>& operator=(const NullableAllocatedValue& rhs);
        // Assign to this object the value of the specified 'rhs', and return a
        // reference providing modifiable access to this object.

    TYPE& operator=(const TYPE& rhs);
        // Assign to this object the value of the specified 'rhs', and return a
        // reference providing modifiable access to the underlying 'TYPE'
        // object.

    void swap(NullableAllocatedValue& 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'.

    TYPE& makeValue(const TYPE& value);
        // Assign to this object the specified 'value', and return a reference
        // providing modifiable access to the underlying 'TYPE' object.

    TYPE& makeValue();
        // Assign to this object the default value for 'TYPE', and return a
        // reference providing modifiable access to the underlying 'TYPE'
        // object.

    template <class STREAM>
    STREAM& bdexStreamIn(STREAM& stream, int version);
        // Assign to this object the value read from the specified input
        // 'stream' using the specified 'version' format, and return a
        // reference to 'stream'.  If 'stream' is initially invalid, this
        // operation has no effect.  If 'version' is not supported, this object
        // is unaltered and 'stream' is invalidated, but otherwise unmodified.
        // If 'version' is supported but 'stream' becomes invalid during this
        // operation, this object has an undefined, but valid, state.  Note
        // that no version is read from 'stream'.  See the 'bslx' package-level
        // documentation for more information on BDEX streaming of
        // value-semantic types and containers.

    void reset();
        // Reset this object to the default constructed state (i.e., to have
        // the null value).

    TYPE& value();
        // Return a reference providing modifiable access to the underlying
        // 'TYPE' object.  The behavior is undefined unless this object is
        // non-null.

    // ACCESSORS
    template <class STREAM>
    STREAM& bdexStreamOut(STREAM& stream, int version) const;
        // Write the value of this object, using the specified 'version'
        // format, to the specified output 'stream', and return a reference to
        // 'stream'.  If 'stream' is initially invalid, this operation has no
        // effect.  If 'version' is not supported, 'stream' is invalidated, but
        // otherwise unmodified.  Note that 'version' is not written to
        // 'stream'.  See the 'bslx' package-level documentation for more
        // information on BDEX streaming of value-semantic types and
        // containers.

    bool isNull() const;
        // Return 'true' if this object is null, and 'false' otherwise.

    int maxSupportedBdexVersion(int versionSelector) const;
        // Return the maximum valid BDEX format version, as indicated by the
        // specified 'versionSelector', to be passed to the 'bdexStreamOut'
        // method.  Note that it is highly recommended that 'versionSelector'
        // be formatted as "YYYYMMDD", a date representation.  Also note that
        // 'versionSelector' should be a *compile*-time-chosen value that
        // selects a format version supported by both externalizer and
        // unexternalizer.  See the 'bslx' package-level documentation for more
        // information on BDEX streaming of value-semantic types and
        // containers.

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
    int maxSupportedBdexVersion() const;
        // Return the most current BDEX streaming version number supported by
        // this class.  (See the package-group-level documentation for more
        // information on BDEX streaming of container types.)
#endif  // BDE_OMIT_INTERNAL_DEPRECATED

                              // Aspects

    bslma::Allocator *allocator() const;
        // Return the allocator used by this object to supply memory.

    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 '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.

    const TYPE& value() const;
        // Return a reference providing non-modifiable access to the underlying
        // 'TYPE' object.  The behavior is undefined unless this object is
        // non-null.
};

// FREE OPERATORS
template <class LHS_TYPE, class RHS_TYPE>
bool operator==(const NullableAllocatedValue<LHS_TYPE>& lhs,
                const NullableAllocatedValue<RHS_TYPE>& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' nullable objects have the
    // same value, and 'false' otherwise.  Two nullable objects have the same
    // value if both are null, or if both are non-null and the values of their
    // underlying objects compare equal.  Note that this function will fail to
    // compile if 'LHS_TYPE' and 'RHS_TYPE' are not compatible.

template <class LHS_TYPE, class RHS_TYPE>
bool operator!=(const NullableAllocatedValue<LHS_TYPE>& lhs,
                const NullableAllocatedValue<RHS_TYPE>& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' nullable objects do not
    // have the same value, and 'false' otherwise.  Two nullable objects do not
    // have the same value if one is null and the other is non-null, or if both
    // are non-null and the values of their underlying objects do not compare
    // equal.  Note that this function will fail to compile if 'LHS_TYPE' and
    // 'RHS_TYPE' are not compatible.

template <class TYPE>
bsl::ostream& operator<<(bsl::ostream&                       stream,
                         const NullableAllocatedValue<TYPE>& object);
    // Write the value of the specified 'object' to the specified output
    // 'stream' in a single-line format, and return a reference to 'stream'.
    // If 'stream' is not valid on entry, this operation has no effect.  Note
    // that this human-readable format is not fully specified, can change
    // without notice, and is logically equivalent to:
    //..
    //  print(stream, 0, -1);
    //..

// FREE FUNCTIONS
template <class TYPE>
void swap(NullableAllocatedValue<TYPE>& a,
          NullableAllocatedValue<TYPE>& 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.

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

                  // ----------------------------------
                  // class NullableAllocatedValue<TYPE>
                  // ----------------------------------

// CREATORS
template <class TYPE>
inline
NullableAllocatedValue<TYPE>
::NullableAllocatedValue(bslma::Allocator *basicAllocator)
: d_value_p(0)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
}

template <class TYPE>
inline
NullableAllocatedValue<TYPE>
::NullableAllocatedValue(const NullableAllocatedValue<TYPE>&  original,
                         bslma::Allocator                    *basicAllocator)
: d_value_p(0)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
    if (!original.isNull()) {
        makeValue(original.value());
    }
}

template <class TYPE>
inline
NullableAllocatedValue<TYPE>::NullableAllocatedValue(
                                              const TYPE&       value,
                                              bslma::Allocator *basicAllocator)
: d_value_p(0)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
    makeValue(value);
}

template <class TYPE>
inline
NullableAllocatedValue<TYPE>::~NullableAllocatedValue()
{
    reset();
}

// MANIPULATORS
template <class TYPE>
inline
NullableAllocatedValue<TYPE>&
NullableAllocatedValue<TYPE>::operator=(
                                       const NullableAllocatedValue<TYPE>& rhs)
{
    if (!rhs.isNull()) {
        makeValue(rhs.value());
    }
    else {
        reset();
    }

    return *this;
}

template <class TYPE>
inline
TYPE& NullableAllocatedValue<TYPE>::operator=(const TYPE& rhs)
{
    return makeValue(rhs);
}

template <class TYPE>
void NullableAllocatedValue<TYPE>::swap(NullableAllocatedValue& other)
{
    // Member 'swap' is undefined for non-equal allocators.

    BSLS_ASSERT(allocator() == other.allocator());

    // Nothing to do if both objects are null.

    if (isNull() && other.isNull()) {
        return;                                                       // RETURN
    }

    // Otherwise, simply swap the pointers to the out-of-place objects.

    bslalg::SwapUtil::swap(&d_value_p, &other.d_value_p);
}

template <class TYPE>
inline
TYPE& NullableAllocatedValue<TYPE>::makeValue(const TYPE& value)
{
    if (d_value_p) {
        *d_value_p = value;
        return *d_value_p;                                            // RETURN
    }

    TYPE *tmpPtr = reinterpret_cast<TYPE *>(
                                        d_allocator_p->allocate(sizeof(TYPE)));
    bslma::DeallocatorProctor<bslma::Allocator> proctor(tmpPtr, d_allocator_p);
    bslalg::ScalarPrimitives::copyConstruct(tmpPtr, value, d_allocator_p);
    proctor.release();
    d_value_p = tmpPtr;

    return *d_value_p;
}

template <class TYPE>
inline
TYPE& NullableAllocatedValue<TYPE>::makeValue()
{
    reset();

    // Note that this alternative implementation, instead of 'reset()',
    // provides stronger exception-safety, but it breaks some client code that
    // uses 'NullableAllocatedValue' with a non-value-semantic 'TYPE'.
    //..
    //  if (d_value_p) {
    //      *d_value_p = TYPE(d_allocator_p);
    //      return *d_value_p;                                        // RETURN
    //  }
    //..

    TYPE *value = reinterpret_cast<TYPE *>(
                                        d_allocator_p->allocate(sizeof(TYPE)));
    bslma::DeallocatorProctor<bslma::Allocator> proctor(value, d_allocator_p);
    bslalg::ScalarPrimitives::defaultConstruct(value, d_allocator_p);
    proctor.release();
    d_value_p = value;

    return *d_value_p;
}

template <class TYPE>
template <class STREAM>
STREAM& NullableAllocatedValue<TYPE>::bdexStreamIn(STREAM& stream, int version)
{
    using bslx::InStreamFunctions::bdexStreamIn;

    char isNull = 0; // Redundant initialization to suppress -Werror.

    stream.getInt8(isNull);

    if (stream) {
        if (!isNull) {
            makeValue();
            bdexStreamIn(stream, value(), version);
        }
        else {
            reset();
        }
    }

    return stream;
}

template <class TYPE>
inline
void NullableAllocatedValue<TYPE>::reset()
{
    if (d_value_p) {
        d_value_p->~TYPE();
        d_allocator_p->deallocate(d_value_p);
        d_value_p = 0;
    }
}

template <class TYPE>
inline
TYPE& NullableAllocatedValue<TYPE>::value()
{
    BSLS_ASSERT(!isNull());

    return *d_value_p;
}

// ACCESSORS
template <class TYPE>
template <class STREAM>
STREAM& NullableAllocatedValue<TYPE>::bdexStreamOut(STREAM& stream,
                                                    int     version) const
{
    using bslx::OutStreamFunctions::bdexStreamOut;

    stream.putInt8(isNull() ? 1 : 0);

    if (!isNull()) {
        bdexStreamOut(stream, value(), version);
    }

    return stream;
}

template <class TYPE>
inline
bool NullableAllocatedValue<TYPE>::isNull() const
{
    return 0 == d_value_p;
}

template <class TYPE>
inline
int NullableAllocatedValue<TYPE>::maxSupportedBdexVersion(
                                                     int versionSelector) const
{
    using bslx::VersionFunctions::maxSupportedBdexVersion;

    // We need to call the 'bslx::VersionFunctions' helper function, because we
    // cannot guarantee that 'TYPE' implements 'maxSupportedBdexVersion' as a
    // class method.

    return maxSupportedBdexVersion(reinterpret_cast<TYPE *>(0),
                                   versionSelector);
}

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
template <class TYPE>
inline
int NullableAllocatedValue<TYPE>::maxSupportedBdexVersion() const
{
    return maxSupportedBdexVersion(0);
}
#endif  // BDE_OMIT_INTERNAL_DEPRECATED

                                  // Aspects

template <class TYPE>
inline
bslma::Allocator *NullableAllocatedValue<TYPE>::allocator() const
{
    return d_allocator_p;
}

template <class TYPE>
inline
bsl::ostream& NullableAllocatedValue<TYPE>::print(
                                            bsl::ostream& stream,
                                            int           level,
                                            int           spacesPerLevel) const
{
    if (isNull()) {
        return bdlb::PrintMethods::print(stream,
                                         "NULL",
                                         level,
                                         spacesPerLevel);             // RETURN
    }

    return bdlb::PrintMethods::print(stream, value(), level, spacesPerLevel);
}

template <class TYPE>
inline
const TYPE& NullableAllocatedValue<TYPE>::value() const
{
#ifndef BDE_OMIT_INTERNAL_DEPRECATED
    // TBD
    // The assert below was commented out because a call to this function is
    // sometimes used as an argument to a template function that only looks at
    // the value type (and does not access the value).

    // BSLS_REVIEW(!isNull());
#else
    BSLS_ASSERT(!isNull());
#endif

    return *d_value_p;
}

}  // close package namespace

// FREE OPERATORS
template <class LHS_TYPE, class RHS_TYPE>
inline
bool bdlb::operator==(const NullableAllocatedValue<LHS_TYPE>& lhs,
                      const NullableAllocatedValue<RHS_TYPE>& rhs)
{
    if (!lhs.isNull() && !rhs.isNull()) {
        return lhs.value() == rhs.value();                            // RETURN
    }

    return lhs.isNull() == rhs.isNull();
}

template <class LHS_TYPE, class RHS_TYPE>
inline
bool bdlb::operator!=(const NullableAllocatedValue<LHS_TYPE>& lhs,
                      const NullableAllocatedValue<RHS_TYPE>& rhs)
{
    if (!lhs.isNull() && !rhs.isNull()) {
        return lhs.value() != rhs.value();                            // RETURN
    }

    return lhs.isNull() != rhs.isNull();
}

template <class TYPE>
inline
bsl::ostream& bdlb::operator<<(bsl::ostream&                       stream,
                               const NullableAllocatedValue<TYPE>& object)
{
    return object.print(stream, 0, -1);
}

// FREE FUNCTIONS
template <class TYPE>
void bdlb::swap(NullableAllocatedValue<TYPE>& a,
                NullableAllocatedValue<TYPE>& b)
{
    if (a.allocator() == b.allocator()) {
        a.swap(b);

        return;                                                       // RETURN
    }

    NullableAllocatedValue<TYPE> futureA(b, a.allocator());
    NullableAllocatedValue<TYPE> futureB(a, b.allocator());

    futureA.swap(a);
    futureB.swap(b);
}

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