// balm_category.h                                                    -*-C++-*-
#ifndef INCLUDED_BALM_CATEGORY
#define INCLUDED_BALM_CATEGORY

#include <bsls_ident.h>
BSLS_IDENT("$Id: balm_category.h,v 1.4 2008/04/16 20:00:49 hversche Exp $")

//@PURPOSE: Provide a representation of a metric category.
//
//@CLASSES:
//    balm::Category: a representation of a metric category
//
//@SEE_ALSO: balm_metricregistry, balm_metricid, balm_metricdescription
//
//@DESCRIPTION: This component provides a class, `balm::Category`, whose values
// are used to categorize collected metrics.  A metric "category" is an
// identifier (chosen by the application) that groups together one or more
// metrics.  A `balm::Category` object contains the address of a string holding
// the name of the category and a boolean value indicating whether the
// category is currently enabled.  The `balm::Category` class suppresses copy
// construction and assignment, and does not provide equality operators;
// applications should use a *single* `balm::Category` object instance per
// category (such as one provided by the *`balm::MetricRegistry`* component).
//
// IMPORTANT: The category name, whose type is `const char *`, must remain
// constant and valid throughout the lifetime of the `balm::Category` object.
//
///Alternative Systems for Telemetry
///---------------------------------
// Bloomberg software may alternatively use the GUTS telemetry API, which is
// integrated into Bloomberg infrastructure.
//
///Thread Safety
///-------------
// `balm::Category` is generally *const* *thread-safe*, meaning that accessors
// may be invoked concurrently from different threads, but it is not safe to
// access or modify a `balm::Category` in one thread while another thread
// modifies the same object.  However, it is safe to *access* the `enabled`
// property on one (or more) thread(s) while the object is being modified on
// another thread.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Basic Usage
/// - - - - - - - - - - -
// The following example demonstrates how to create, access, and modify a
// `balm::Category` object.  We start by creating three category objects with
// different names:
// ```
// balm::Category categoryA("A", true);
// balm::Category categoryB("B", false);
// balm::Category categoryC("C");
// ```
// Once the category objects have been created, we can use the `name` and
// `enabled` methods to access their values:
// ```
// assert(0 == bsl::strcmp("A", categoryA.name()));
// assert(0 == bsl::strcmp("B", categoryB.name()));
// assert(0 == bsl::strcmp("C", categoryC.name()));
//
// assert( categoryA.enabled());
// assert(!categoryB.enabled());
// assert( categoryC.enabled());
// ```
// Finally, we modify the enablement status of one of the categories, and then
// write all three categories to the console:
// ```
// categoryC.setEnabled(false);
//
// bsl::cout << "categoryA: " << categoryA << bsl::endl
//           << "categoryB: " << categoryB << bsl::endl
//           << "categoryC: " << categoryC << bsl::endl;
// ```
// With the resulting console output:
// ```
// categoryA: [ A ENABLED ]
// categoryB: [ B DISABLED ]
// categoryC: [ C DISABLED ]
// ```

#include <balscm_version.h>

#include <bsls_atomic.h>
#include <bsls_libraryfeatures.h>

#include <bsl_iosfwd.h>

namespace BloombergLP {


namespace balm {

class CategoryHolder;

                               // ==============
                               // class Category
                               // ==============

/// This class provides a mechanism for representing a category.  A category
/// is an identifier used to group related metrics.  A `Category` object
/// contains the address of a null-terminated string, `name`, holding the
/// name of the category and a boolean value, `enabled`, indicating whether
/// the category is currently enabled.
class Category {

    // DATA
    const char     *d_name_p;     // name of the category (held, not owned)

    bsls::AtomicInt d_enabled;    // whether the category is enabled

    CategoryHolder *d_holders_p;  // linked list of holders of this category

    // NOT IMPLEMENTED
    Category(const Category& );
    Category& operator=(const Category& );

  public:
    // CREATORS

    /// Create a category having the specified `name` address.  Optionally
    /// specify `enabledFlag`, the enabled status of the category; if
    /// `enabledFlag` is not specified, the `enabled` status is `true`.  The
    /// behavior is undefined unless `name` remains valid and unmodified for
    /// the lifetime of this object.
    explicit Category(const char *name, bool enabledFlag = true);

    /// Destroy this category object.
    ~Category();

    // MANIPULATORS

    /// Set the name of this metric to the specified `name` address.  The
    /// behavior is undefined unless `name` remains valid and unmodified
    /// for the lifetime of this object.
    void setName(const char *name);

    /// Set the `enabled` state of this category to the value of the
    /// specified `enabledFlag` and update any `CategoryHolder` objects
    /// registered with this category.  Note that this operation is *not*
    /// atomic, and other threads may simultaneously access the current
    /// enabled value while this operation is performed.  Also note that
    /// this operation has *linear* performance with respect to the number
    /// of registered category holders for `category`.
    void setEnabled(bool enabledFlag);

    /// Load into the specified `holder` the address of this category, its
    /// `enabled()` status, and the address of the next holder in the
    /// linked list of category holders maintained by this object
    /// (prepending `holder` to this category's linked list of category
    /// holders).  This category will update `holder->enabled()` when its
    /// enabled state changes, and will reset `holder` (i.e.,
    /// `holder->reset()`) when this category is destroyed.  The behavior
    /// is undefined unless `holder` remains valid and *unmodified* (by the
    /// client) for the lifetime of this object and is *not* registered
    /// again with any category (including this one).
    void registerCategoryHolder(CategoryHolder *holder);

    // ACCESSORS

    /// Return the address of the non-modifiable null-terminated string
    /// containing the name of this category.
    const char *name() const;

    /// Report whether this category is enabled. This function is fully
    /// thread-safe.
    bool enabled() const;

    /// Return a *reference* to a const value indicating the enabled status
    /// of this category, allowing downstream uses to minimize latency by
    /// avoiding indirection through abstracted interfaces, albeit at some
    /// risk of object-lifetime violations.  The returned reference must not
    /// be allowed to outlive this category object.
    const bsls::AtomicInt& isEnabledRaw() const;

    /// Print this category to the specified output `stream` in some human
    /// readable form, and return the modifiable `stream`.
    bsl::ostream& print(bsl::ostream& stream) const;
};

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

// FREE OPERATORS

/// Output a formatted description of the specified `rhs` category to the
/// specified `stream`, and return the modifiable `stream`.
inline
bsl::ostream& operator<<(bsl::ostream& stream, const Category& rhs);

                            // ====================
                            // class CategoryHolder
                            // ====================

/// This class, informally referred to as a "category holder" (or simply
/// "holder"), holds a category, an enabled value, and a pointer to a
/// "next" holder.  Both the category and next pointer may be null.  The
/// intended use is as follows: (1) instances of this class are (only)
/// declared in contexts where collecting a metric occurs; (2) if the
/// enabled value is true, the category contains the address of a valid,
/// enabled, category; (3) if the next pointer is non-null, then the holder
/// pointed to holds the same category and threshold.  Instances of this
/// class must be *statically* initializable.  Hence, the data members are
/// `public`, and no constructors or destructor are defined.
///
/// This class should *not* be used directly by client code.  It is an
/// implementation detail of the `balm` metric collection system.
class CategoryHolder {

    // NOT IMPLEMENTED
    CategoryHolder& operator=(const CategoryHolder& rhs);

  public:

    // PUBLIC DATA MEMBERS

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_BASELINE_LIBRARY
    bsls::AtomicBool                    d_enabled;     // whether the category
                                                       // is enabled

    bsls::AtomicPointer<const Category> d_category_p;  // held category (not
                                                       // owned)

    bsls::AtomicPointer<CategoryHolder> d_next_p;      // next category holder
                                                       // in linked list
#else
    bool            d_enabled;     // whether the category is enabled
    const Category *d_category_p;  // held category (not owned)
    CategoryHolder *d_next_p;      // next category holder in linked list
#endif

    // CREATORS

    // No constructors or destructors are declared in order to allow for static
    // initialization of instances of this class.

    // MANIPULATORS

    /// Reset this object to its default value.  The default value is:
    /// ```
    ///  { false, 0, 0 }
    /// ```
    void reset();

    /// Set the address of the category held by this holder to the specified
    /// `category`.
    void setCategory(const Category *category);

    /// Set the `enabled` state of this category to the value of the
    /// specified `enabledFlag`.
    void setEnabled(bool enabledFlag);

    /// Set this holder to point to the specified `holder`.
    void setNext(CategoryHolder *holder);

    // ACCESSORS

    /// Return the address of the non-modifiable category held by this
    /// holder.
    const Category *category() const;

    /// Return `true` if `category` is valid (i.e., non-null) and enabled,
    /// and `false` otherwise.
    bool enabled() const;

    /// Return the address of the modifiable holder pointed to by this
    /// holder.
    CategoryHolder *next() const;
};

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

                               // --------------
                               // class Category
                               // --------------

// CREATORS
inline
Category::Category(const char *name, bool enabledFlag)
: d_name_p(name)
, d_enabled(enabledFlag)
, d_holders_p(0)
{
}

// MANIPULATORS
inline
void Category::setName(const char *name)
{
    d_name_p = name;
}

// ACCESSORS
inline
const char *Category::name() const
{
    return d_name_p;
}

inline
bool Category::enabled() const
{
    return d_enabled;
}

inline
const bsls::AtomicInt& Category::isEnabledRaw() const
{
    return d_enabled;
}

                            // --------------------
                            // class CategoryHolder
                            // --------------------

// MANIPULATORS
inline
void CategoryHolder::setCategory(const Category *category)
{
    d_category_p = category;
}

inline
void CategoryHolder::setEnabled(bool enabledFlag)
{
    d_enabled = enabledFlag;
}

inline
void CategoryHolder::setNext(CategoryHolder *holder)
{
    d_next_p = holder;
}

// ACCESSORS
inline
const Category *CategoryHolder::category() const
{
    return d_category_p;
}

inline
bool CategoryHolder::enabled() const
{
    return d_enabled;
}

inline
CategoryHolder *CategoryHolder::next() const
{
    return d_next_p;
}
}  // close package namespace

// FREE OPERATORS
inline
bsl::ostream& balm::operator<<(bsl::ostream& stream, const Category& rhs)
{
    return rhs.print(stream);
}

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