// bslma_stdtestallocator.h                                           -*-C++-*-
#ifndef INCLUDED_BSLMA_STDTESTALLOCATOR
#define INCLUDED_BSLMA_STDTESTALLOCATOR

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

//@PURPOSE: Provide stl-compatible, `bslma`-style allocator to track usage.
//
//@CLASSES:
//  bslma::StdTestAllocator: instrumented `bslma-style` stl allocator template
//
//@MACROS:
//
//@SEE_ALSO: TBD
//
//@DESCRIPTION: TBD

#include <bslscm_version.h>

#include <bslma_constructionutil.h>
#include <bslma_destructionutil.h>
#include <bslma_isstdallocator.h>
#include <bslma_testallocator.h>
#include <bslma_default.h>

#include <bslmf_isbitwiseequalitycomparable.h>
#include <bslmf_isbitwisecopyable.h>
#include <bslmf_isbitwisemoveable.h>
#include <bslmf_issame.h>
#include <bslmf_nestedtraitdeclaration.h>

namespace BloombergLP {
namespace bslma {

                           // ======================
                           // class StdTestAllocator
                           // ======================

/// An STL-compatible test allocator that forwards allocation calls to an
/// underlying mechanism object of a type derived from
/// `bslma::TestAllocator`.  This class template adheres to the allocator
/// requirements defined in section 20.1.5 [lib.allocator.requirements] of
/// the C++ standard and may be used to instantiate any [container] class
/// template that follows the STL allocator protocol.  The allocation
/// mechanism is chosen at run-time, giving the programmer run-time control
/// over how a container allocates and frees memory.
template <class TYPE>
class StdTestAllocator {

    // DATA
    TestAllocator *d_mechanism;

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(StdTestAllocator, bslma::IsStdAllocator);
    BSLMF_NESTED_TRAIT_DECLARATION(StdTestAllocator,
                                   bslmf::IsBitwiseCopyable);
    BSLMF_NESTED_TRAIT_DECLARATION(StdTestAllocator,
                                   bslmf::IsBitwiseMoveable);
    BSLMF_NESTED_TRAIT_DECLARATION(StdTestAllocator,
                                   bslmf::IsBitwiseEqualityComparable);
        // Declare nested type traits for this class.

    // PUBLIC TYPES
    typedef std::size_t     size_type;
    typedef std::ptrdiff_t  difference_type;
    typedef TYPE           *pointer;
    typedef const TYPE     *const_pointer;
    typedef typename bsl::conditional<bsl::is_void<TYPE>::value,
                                      void,
                                      TYPE&>::type reference;
    typedef typename bsl::conditional<bsl::is_void<TYPE>::value,
                                      void,
                                      const TYPE&>::type const_reference;
    // typedef TYPE&           reference;
    // typedef const TYPE&     const_reference;
    typedef TYPE            value_type;

    /// This nested `struct` template, parameterized by `ANY_TYPE`, provides
    /// a namespace for an `other` type alias, which is a `StdTestAllocator`
    /// type following the same template as this one but that allocates
    /// elements of `ANY_TYPE`.  Note that this `StdTestAllocator` type is
    /// convertible to and from `other` for any type, including `void`.
    template <class ANY_TYPE>
    struct rebind {

        typedef StdTestAllocator<ANY_TYPE> other;
    };

    // CREATORS

    /// TBD: fix comment to mention that the default allocator has to be
    /// a test allocator.
    /// Create a proxy object which will forward allocation calls to the
    /// object pointed to by `bslma::Default::defaultAllocator()`.
    /// Postcondition:
    /// ```
    /// this->mechanism() == bslma::Default::defaultAllocator();
    /// ```
    StdTestAllocator();

    /// TBD: fix comment to mention that `mechanism` has to be a test
    /// allocator.
    /// Convert a `bslma::Allocator` pointer to an test allocator object
    /// which forwards allocation calls to the object pointed to by the
    /// specified `mechanism`.  If `mechanism` is 0, then the currently
    /// installed default allocator is used instead.  Postcondition:
    /// `0 == mechanism || this->mechanism() == mechanism`.
    StdTestAllocator(Allocator *mechanism);                         // IMPLICIT

    /// Create a proxy object using the same mechanism as the specified
    /// `original`.  Postcondition: `this->mechanism() == rhs.mechanism()`.
    StdTestAllocator(const StdTestAllocator& original);

    /// Create a proxy object sharing the same mechanism object as the
    /// specified `rhs`.  The newly constructed test allocator will compare
    /// equal to `rhs`, even though they are instantiated on different
    /// types.  Postcondition: `this->mechanism() == rhs.mechanism()`.
    template <class ANY_TYPE>
    StdTestAllocator(const StdTestAllocator<ANY_TYPE>& rhs);

    //! ~StdTestAllocator();
        // Destroy this object.  Note that this does not delete the object
        // pointed to by 'mechanism()'.  Also note that this method's
        // definition is compiler generated.

    //! StdTestAllocator& operator=(const StdTestAllocator& rhs);
        // Assign to this object the value of the specified 'rhs'.
        // Postcondition: 'this->mechanism() == rhs->mechanism()'.  Note that
        // this does not delete the object pointed to by the previous value of
        // 'mechanism()'.  Also note that this method's definition is compiler
        // generated.

    // MANIPULATORS

    /// Allocate enough (properly aligned) space for the specified `n`
    /// objects of (template parameter) `TYPE` by calling `allocate` on the
    /// mechanism object.  The optionally specified `hint` argument is
    /// ignored by this test allocator type.  The behavior is undefined
    /// unless `n <= max_size()`.
    pointer allocate(size_type n, const void *hint = 0);

    /// Return memory previously allocated with `allocate` to the underlying
    /// mechanism object by calling `deallocate` on the mechanism object
    /// with the specified `p`.  The optionally specified `n` argument is
    /// ignored by this test allocator type.
    void deallocate(pointer p, size_type n = 1);

    /// Copy-construct an object of (template parameter) `TYPE` from the
    /// specified `val` at the memory address specified by `p`.  Do not
    /// directly allocate memory.  The behavior is undefined unless `p` is
    /// not properly aligned for objects of the given `TYPE`.
    template <class ELEMENT_TYPE>
    void construct(ELEMENT_TYPE *address, const TYPE& val);

    /// Call the `TYPE` destructor for the object pointed to by the
    /// specified `p`.  Do not directly deallocate any memory.
    template <class ELEMENT_TYPE>
    void destroy(ELEMENT_TYPE *address);

    // ACCESSORS

    /// Return the address of the object referred to by the specified `x`,
    /// even if the (template parameter) `TYPE` overloads the unary
    /// `operator&`.
    pointer address(reference x) const;

    /// Return the address of the object referred to by the specified `x`,
    /// even if the (template parameter) `TYPE` overloads the unary
    /// `operator&`.
    const_pointer address(const_reference x) const;

    /// Return the maximum number of elements of (template parameter) `TYPE`
    /// that can be allocated using this test allocator.  Note that there is
    /// no guarantee that attempts at allocating fewer elements than the
    /// value returned by `max_size` will not throw.
    size_type max_size() const;

    /// Return a pointer to the mechanism object to which this proxy
    /// forwards allocation and deallocation calls.
    TestAllocator *mechanism() const;
};

// FREE OPERATORS

/// Return `true` if the specified `lhs` and `rhs` are proxies for the same
/// `bslma::TestAllocator` object.  This is a practical implementation of
/// the STL requirement that two allocators compare equal if and only if
/// memory allocated from one can be deallocated from the other.  Note that
/// the two allocators need not be instantiated on the same type in order to
/// compare equal.
template <class T1, class T2>
inline
bool operator==(const StdTestAllocator<T1>& lhs,
                const StdTestAllocator<T2>& rhs);

/// Return `true` unless the specified `lhs` and `rhs` are proxies for the
/// same `bslma::TestAllocator` object, in which case return `false`.  This
/// is a practical implementation of the STL requirement that two allocators
/// compare equal if and only if memory allocated from one can be
/// deallocated from the other.  Note that the two allocators need not be
/// instantiated on the same type in order to compare equal.
template <class T1, class T2>
inline
bool operator!=(const StdTestAllocator<T1>& lhs,
                const StdTestAllocator<T2>& rhs);

/// Return `true` if the specified `lhs` is a proxy for the specified `rhs`,
/// and `false` otherwise.
template <class TYPE>
inline
bool operator==(const StdTestAllocator<TYPE>& lhs,
                const TestAllocator          *rhs);

/// Return `true` unless the specified `lhs` is a proxy for the specified
/// `rhs`, in which case return `false`.
template <class TYPE>
inline
bool operator!=(const StdTestAllocator<TYPE>& lhs,
                const TestAllocator          *rhs);

/// Return `true` if the specified `rhs` is a proxy for the specified `lhs`,
/// and `false` otherwise.
template <class TYPE>
inline
bool operator==(const TestAllocator          *lhs,
                const StdTestAllocator<TYPE>& rhs);

/// Return `true` unless the specified `rhs` is a proxy for the specified
/// `lhs`, in which case return `false`.
template <class TYPE>
inline
bool operator!=(const TestAllocator          *lhs,
                const StdTestAllocator<TYPE>& rhs);


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

                             // ----------------------
                             // class StdTestAllocator
                             // ----------------------

// LOW-LEVEL ACCESSORS
template <class TYPE>
inline
TestAllocator *StdTestAllocator<TYPE>::mechanism() const
{
    return d_mechanism;
}

// CREATORS
template <class TYPE>
inline
StdTestAllocator<TYPE>::StdTestAllocator()
: d_mechanism(dynamic_cast<TestAllocator *>(Default::defaultAllocator()))
{
    BSLS_ASSERT_SAFE(d_mechanism);
}

template <class TYPE>
inline
StdTestAllocator<TYPE>::StdTestAllocator(Allocator *mechanism)
: d_mechanism(dynamic_cast<TestAllocator *>(Default::allocator(mechanism)))
{
    BSLS_ASSERT_SAFE(d_mechanism);
}

template <class TYPE>
inline
StdTestAllocator<TYPE>::StdTestAllocator(const StdTestAllocator& original)
: d_mechanism(original.mechanism())
{
    BSLS_ASSERT_SAFE(d_mechanism);
}

template <class TYPE>
template <class ANY_TYPE>
inline
StdTestAllocator<TYPE>::StdTestAllocator(const StdTestAllocator<ANY_TYPE>& rhs)
: d_mechanism(rhs.mechanism())
{
    BSLS_ASSERT_SAFE(d_mechanism);
}

// MANIPULATORS
template <class TYPE>
inline
typename StdTestAllocator<TYPE>::pointer
StdTestAllocator<TYPE>::allocate(typename StdTestAllocator::size_type  n,
                          const void                    *hint)
{
    BSLS_ASSERT_SAFE(n <= this->max_size());

    (void) hint;  // suppress unused parameter warning
    return static_cast<pointer>(d_mechanism->allocate(n * sizeof(TYPE)));
}

template <class TYPE>
inline
void StdTestAllocator<TYPE>::deallocate(typename StdTestAllocator::pointer   p,
                                        typename StdTestAllocator::size_type n)
{
    (void) n;  // suppress unused parameter warning
    d_mechanism->deallocate(p);
}

template <class TYPE>
template <class ELEMENT_TYPE>
inline
void StdTestAllocator<TYPE>::construct(ELEMENT_TYPE *address, const TYPE& val)
{
    new (static_cast<void*>(address)) ELEMENT_TYPE(val);
}

template <class TYPE>
template <class ELEMENT_TYPE>
inline
void StdTestAllocator<TYPE>::destroy(ELEMENT_TYPE *address)
{
    DestructionUtil::destroy(address);
}

// ACCESSORS
template <class TYPE>
inline
typename StdTestAllocator<TYPE>::const_pointer
StdTestAllocator<TYPE>::address(const_reference x) const
{
    return BSLS_UTIL_ADDRESSOF(x);
}

template <class TYPE>
inline
typename StdTestAllocator<TYPE>::pointer
StdTestAllocator<TYPE>::address(reference x) const
{
    return BSLS_UTIL_ADDRESSOF(x);
}

template <class TYPE>
inline
typename StdTestAllocator<TYPE>::size_type
StdTestAllocator<TYPE>::max_size() const
{
    // Return the largest value, 'v', such that 'v * sizeof(T)' fits in a
    // 'size_type'.

    // We will calculate MAX_NUM_BYTES based on our knowledge that
    // 'bslma::Allocator::size_type' is just an alias for 'std::size_t'.  First
    // demonstrate that is true:

    BSLMF_ASSERT((bsl::is_same<Allocator::size_type, std::size_t>::value));

    static const std::size_t MAX_NUM_BYTES    = ~std::size_t(0);
    static const std::size_t MAX_NUM_ELEMENTS = MAX_NUM_BYTES / sizeof(TYPE);

    return MAX_NUM_ELEMENTS;
}

// FREE OPERATORS
template <class T1, class T2>
inline
bool operator==(const StdTestAllocator<T1>& lhs,
                const StdTestAllocator<T2>& rhs)
{
    return lhs.mechanism() == rhs.mechanism();
}

template <class T1, class T2>
inline
bool operator!=(const StdTestAllocator<T1>& lhs,
                const StdTestAllocator<T2>& rhs)
{
    return ! (lhs == rhs);
}

template <class TYPE>
inline
bool operator==(const StdTestAllocator<TYPE>& lhs,
                const TestAllocator          *rhs)
{
    return lhs.mechanism() == rhs;
}

template <class TYPE>
inline
bool operator!=(const StdTestAllocator<TYPE>& lhs,
                const TestAllocator          *rhs)
{
    return ! (lhs == rhs);
}

template <class TYPE>
inline
bool operator==(const TestAllocator          *lhs,
                const StdTestAllocator<TYPE>& rhs)
{
    return lhs == rhs.mechanism();
}

template <class TYPE>
inline
bool operator!=(const TestAllocator          *lhs,
                const StdTestAllocator<TYPE>& rhs)
{
    return ! (lhs == rhs);
}

}  // close package namespace
}  // close enterprise namespace

#endif

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