// ball_attributecontext.h                                            -*-C++-*-
#ifndef INCLUDED_BALL_ATTRIBUTECONTEXT
#define INCLUDED_BALL_ATTRIBUTECONTEXT

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

//@PURPOSE: Provide a container for storing attributes and caching results.
//
//@CLASSES:
//         ball::AttributeContext: thread-local list of attribute containers
//  ball::AttributeContextProctor: proctor for deleting an attribute context
//
//@SEE_ALSO: ball_attributecontainer
//
//@DESCRIPTION: This component provides a mechanism, `ball::AttributeContext`,
// used for storing attributes in thread-local storage and evaluating rules
// associated with a given category using those stored attributes, and a scoped
// proctor, `ball::AttributeContextProctor`, used for destroying the attribute
// context of the current thread.
//
// This component participates in the implementation of "Rule-Based Logging".
// For more information on how to use that feature, please see the package
// level documentation and usage examples for "Rule-Based Logging".
//
// Clients obtain the attribute context for the current thread by calling the
// `getContext` class method.  Attributes are added and removed from an
// attribute context using the `addAttributes` and `removeAttributes` methods,
// respectively.  Additionally, `ball::AttributeContext` provides methods, used
// primarily by other components in the `ball` package' (see {Active Rules}
// below), to determine the effect of the current logging rules on the logging
// thresholds of a category.
//
// `ball::AttributeContext` contains class data members that must be
// initialized, using the `initialize` class method, with a `CategoryManager`
// object containing a `RuleSet` representing the currently installed (global)
// logging rules for the process.  However, clients generally should not call
// `initialize` directly.  Instead, `initialize` is called *internally* when
// the logger manager singleton is initialized.
//
///Active Rules
///------------
// `ball::AttributeContext` provides two methods, `hasRelevantActiveRules` and
// `determineThresholdLevels`, that are used to determine the effect of the
// current logging rules maintained by the category manager on the logging
// thresholds of a given category.  Note that these methods are generally
// intended for use by other components in the `ball` package'.
//
// `hasRelevantActiveRules` returns `true` if there is at least one relevant
// and active rule (in the global set of rules) that might modify the logging
// thresholds of the supplied `category`.  A rule is "relevant" if the rule's
// pattern matches the category's name, and a rule is "active" if all the
// attributes defined for that rule are satisfied by the current thread's
// attributes (i.e., `ball::Rule::evaluate` returns `true` for the collection
// of attributes maintained for the current thread by the thread's
// `ball::AttributeContext` object).
//
// `determineThresholdLevels` returns the logging threshold levels for a
// category, factoring in any active rules that apply to the category that
// might override the category's thresholds.
//
///Usage
///-----
// This section illustrates the intended use of `ball::AttributeContext`.
//
///Example 1: Managing Attributes
///- - - - - - - - - - - - - - -
// First we will define a thread function that will create and install two
// attributes.  Note that we will use the `AttributeSet` implementation of the
// `ball::AttributeContainer` protocol defined in the component documentation
// for `ball_attributecontainer`; the `ball` package provides a similar class
// in the `ball_defaultattributecontainer` component.
// ```
// extern "C" void *workerThread1(void *)
// {
// ```
// Inside this thread function, we create an attribute set to hold our
// attribute values, then we create two `ball::Attribute` objects and add them
// to that set:
// ```
//     AttributeSet attributes;
//     ball::Attribute a1("uuid", 4044457);
//     ball::Attribute a2("name", "Gang Chen");
//     attributes.insert(a1);
//     attributes.insert(a2);
// ```
// Next, we obtain a reference to the current thread's attribute context using
// the `getContext` class method (note that in practice we would use a scoped
// guard for this purpose; see {`ball_scopedattributes`}):
// ```
//     ball::AttributeContext *context = ball::AttributeContext::getContext();
//     assert(context);
//     assert(context == ball::AttributeContext::lookupContext());
// ```
// We can add our attribute container, `attributes`, to the current context
// using the `addAttributes` method.  We store the returned iterator so that
// we can remove `attributes` before it goes out of scope and is destroyed:
// ```
//     ball::AttributeContext::iterator it =
//                                        context->addAttributes(&attributes);
//     assert(context->hasAttribute(a1));
//     assert(context->hasAttribute(a2));
// ```
// We then call the `removeAttributes` method to remove the attributes from
// the attribute context:
// ```
//     context->removeAttributes(it);
//     assert(false == context->hasAttribute(a1));
//     assert(false == context->hasAttribute(a2));
// ```
// This completes the first thread function:
// ```
//     return 0;
// }
// ```
// The second thread function will simply verify that there is no currently
// available attribute context.  Note that attribute contexts are created and
// managed by individual threads using thread-specific storage, and that
// attribute contexts created by one thread are not visible in any other
// threads:
// ```
// extern "C" void *workerThread2(void *)
// {
//     assert(0 == ball::AttributeContext::lookupContext());
//     return 0;
// }
// ```
//
///Example 2: Calling `hasRelevantActiveRules` and `determineThresholdLevels`
///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// In this example we demonstrate how to call the `hasRelevantActiveRules` and
// `determineThresholdLevels` methods.  These methods are used (primarily by
// other components in the `ball` package) to determine the effect of the
// current logging rules on the logging thresholds of a category.  Note that a
// rule is "relevant" if the rule's pattern matches the category's name, and a
// rule is "active" if `ball::Rule::evaluate` returns `true` for the
// collection of attributes maintained for the current thread by the thread's
// `ball::AttributeContext` object.
//
// We start by creating a `ball::CategoryManager` and use it to initialize the
// static data members of `ball::AttributeContext`.  Note that, in practice,
// this initialization should *not* be performed by clients of the `ball`
// package: `ball::AttributeContext::initialize` is called *internally* as part
// of the initialization of the `ball::LoggerManager` singleton.
// ```
// ball::CategoryManager categoryManager;
// ball::AttributeContext::initialize(&categoryManager);
// ```
// Next, we add a category to the category manager.  Each created category has
// a name and the logging threshold levels for that category.  The logging
// threshold levels indicate the minimum severity for logged messages that will
// trigger the relevant action.  The four thresholds are the "record level"
// (messages logged with a higher severity than this threshold should be added
// to the current logger's record buffer), the "pass-through level" (messages
// logged with a severity higher than this threshold should be published
// immediately), the "trigger level" (messages logged with a higher severity
// than this threshold should trigger the publication of the entire contents of
// the current logger's record buffer), and the "trigger-all level" (messages
// logged with a higher severity than this threshold should trigger the
// publication of every logger's record buffer), respectively.  Note that
// clients are generally most interested in the "pass-through" threshold level.
// Also note that a higher number indicates a lower severity.
// ```
// const ball::Category *cat1 =
//                 categoryManager.addCategory("MyCategory", 128, 96, 64, 32);
// ```
// Next, we obtain the context for the current thread:
// ```
// ball::AttributeContext *context = ball::AttributeContext::getContext();
// ```
// We call `hasRelevantActiveRules` on `cat1`.  This will be `false` because
// we haven't supplied any rules:
// ```
// assert(!context->hasRelevantActiveRules(cat1));
// ```
// We call `determineThresholdLevels` on `cat1`.  This will simply return the
// logging threshold levels we defined for `cat1` when it was created because
// no rules have been defined that might modify those thresholds:
// ```
// ball::ThresholdAggregate cat1ThresholdLevels(0, 0, 0, 0);
// context->determineThresholdLevels(&cat1ThresholdLevels, cat1);
// assert(128 == cat1ThresholdLevels.recordLevel());
// assert( 96 == cat1ThresholdLevels.passLevel());
// assert( 64 == cat1ThresholdLevels.triggerLevel());
// assert( 32 == cat1ThresholdLevels.triggerAllLevel());
// ```
// Next, we create a rule that will apply to those categories whose names match
// the pattern "My*", where `*` is a wild-card value.  The rule defines a set
// of thresholds levels that may override the threshold levels of those
// categories whose name matches the rule's pattern:
// ```
// ball::Rule myRule("My*", 120, 110, 70, 40);
// categoryManager.addRule(myRule);
// ```
// Now, we call `hasRelevantActiveRules` again for `cat1`, but this time the
// method returns `true` because the rule we just added is both "relevant" to
// `cat1` and "active".  `myRule` is "relevant" to `cat1` because the name of
// `cat1` ("MyCategory") matches the pattern for `myRule` ("My*") (i.e.,
// `myRule` applies to `cat1`).  `myRule` is also "active" because all the
// attributes defined for the rule are satisfied by the current thread (in this
// case the rule has no attributes, so the rule is always "active").  Note
// that we will discuss the meaning of "active" and the use of attributes later
// in this example.
// ```
// assert(context->hasRelevantActiveRules(cat1));
// ```
// Next, we call `determineThresholdLevels` for `cat1`.  This method compares
// the threshold levels defined for a category with those of any active rules
// that apply to that category, and determines the minimum severity (i.e., the
// maximum numerical value) for each respective threshold amongst those values.
// ```
// ball::ThresholdAggregate thresholdLevels(0, 0, 0, 0);
// context->determineThresholdLevels(&thresholdLevels, cat1);
// assert(128 == thresholdLevels.recordLevel());
// assert(110 == thresholdLevels.passLevel());
// assert( 70 == thresholdLevels.triggerLevel());
// assert( 40 == thresholdLevels.triggerAllLevel());
// ```
// In this case the "pass-through", "trigger", and "trigger-all" threshold
// levels defined by `myRule` (110, 70, and 40) are greater (i.e., define a
// lower severity) than those respective values defined for `cat1` (96, 64,
// and 32), so those values override the values defined for `cat1`.  On the
// other hand, the "record" threshold level for `cat1` (128) is greater than
// the value defined by `myRule` (120), so the threshold level defined for
// `cat1` is  returned.  In effect, `myRule` has lowered the severity at which
// messages logged in the "MyCategory" category will be published immediately,
// trigger the publication of the current logger's record buffer, and trigger
// the publication of every logger's record buffer.
//
// Next we modify `myRule`, adding an attribute indicating that the rule should
// only apply if the attribute context for the current thread contains the
// attribute `("uuid", 3938908)`:
// ```
// categoryManager.removeRule(myRule);
// ball::ManagedAttribute attribute("uuid", 3938908);
// myRule.addAttribute(attribute);
// categorymanager.addRule(myRule);
// ```
// When we again call `hasRelevantActiveRules` for `cat1`, it now returns
// `false`.  The rule, `myRule`, still applies to `cat1` (i.e., it is still
// "relevant" to `cat1`), but the attributes defined by `myRule` are no longer
// satisfied by the current thread, i.e., the current thread's attribute
// context does not contain an attribute matching `("uuid", 3938908)`.
// ```
// assert(!context->hasRelevantActiveRules(cat1));
// ```
// Next, we call `determineThresholdLevels` on `cat1` and find that it
// returns the threshold levels we defined for `cat1` when we created it:
// ```
// context->determineThresholdLevels(&thresholdLevels, cat1);
// assert(thresholdLevels == cat1ThresholdLevels);
// ```
// Finally, we add an attribute to the current thread's attribute context (as
// we did in the first example, "Managing Attributes").  Note that we keep an
// iterator referring to the added attributes so that we can remove them before
// `attributes` goes out of scope and is destroyed.  Also note that the class
// `AttributeSet` is defined in the component documentation for
// `ball_attributecontainer`.
// ```
// AttributeSet attributes;
// attributes.insert(ball::Attribute("uuid", 3938908));
// ball::AttributeContext::iterator it = context->addAttributes(&attributes);
// ```
// The following call to `hasRelevantActiveRules` will return `true` for `cat1`
// because there is at least one rule, `myRule`, that is both "relevant"
// (i.e., its pattern matches the category name of `cat1`) and "active" (i.e.,
// all of the attributes defined for `myRule` are satisfied by the attributes
// held by this thread's attribute context):
// ```
// assert(context->hasRelevantActiveRules(cat1));
// ```
// Now, when we call `determineThresholdLevels`, it will again return the
// maximum threshold level from `cat1` and `myRule`:
// ```
// context->determineThresholdLevels(&thresholdLevels, cat1);
// assert(128 == thresholdLevels.recordLevel());
// assert(110 == thresholdLevels.passLevel());
// assert( 70 == thresholdLevels.triggerLevel());
// assert( 40 == thresholdLevels.triggerAllLevel());
// ```
// We must be careful to remove `attributes` from the attribute context before
// it goes out of scope and is destroyed.  Note that the `ball` package
// provides a component, `ball_scopedattributes`, for adding, and automatically
// removing, attributes from the current thread's attribute context.
// ```
// context->removeAttributes(it);
// ```

#include <balscm_version.h>

#include <ball_attributecontainerlist.h>
#include <ball_ruleset.h>

#include <bslma_allocator.h>

#include <bslmt_threadutil.h>

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

#include <bsl_functional.h>
#include <bsl_iosfwd.h>

namespace BloombergLP {
namespace ball {

class Attribute;
class AttributeContainer;
class Category;
class CategoryManager;
class ThresholdAggregate;

             // ==========================================
             // class AttributeContext_RuleEvaluationCache
             // ==========================================

/// This is an implementation type of `AttributeContext` and should not be
/// used by clients of this package.  A rule evaluation cache is a mechanism
/// for evaluating and caching whether a rule is active.  A rule is
/// considered active if all of its attributes are satisfied by the
/// collection of attributes held in a `AttributeContainerList` object
/// (i.e., `Rule::evaluate` returns `true` for the `AttributeContainerList`
/// object).  The rules this cache evaluates are contained in a `RuleSet`
/// object.  `RuleSet::MaskType` is a bit mask for a rule set, where each
/// bit is a boolean value associated with the rule at the corresponding
/// index in a rule set.  An `AttributeContext` determines, using the
/// `isDataAvailable` method, if a particular set of rules (described using
/// a bit mask) have already been evaluated.  A context accesses the current
/// cache of rule evaluations using the `knownActiveRules` method.  Finally,
/// a context updates the cache of rule evaluations using the `update`
/// method.  Note that the `isDataAvailable` method should be used prior to
/// using `knownActiveRules` in order to ensure the relevant rules have been
/// evaluated and that those evaluations are up-to-date.
class AttributeContext_RuleEvaluationCache {

    // DATA
    RuleSet::MaskType  d_evalMask;        // set of bits, each of which
                                          // indicates whether the
                                          // corresponding rule has been
                                          // evaluated and cached in
                                          // `d_resultMask` (1 if evaluated and
                                          // 0 otherwise)

    RuleSet::MaskType  d_resultMask;      // set of bits, each of which caches
                                          // the result of the most recent
                                          // evaluation of the corresponding
                                          // rule (1 if the rule is active and
                                          // 0 otherwise)

    bsls::Types::Int64 d_sequenceNumber;  // sequence number used to determine
                                          // if this cache is in sync with the
                                          // rule set maintained by the
                                          // category manager (see `update`);
                                          // if the sequence number changes it
                                          // indicates the cache is out of date

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

  public:
    // CREATORS

    /// Create an empty rule evaluation cache having a sequence number of -1.
    AttributeContext_RuleEvaluationCache();

    /// Destroy this object.
    //! ~AttributeContext_RuleEvaluationCache() = default;

    // MANIPULATORS

    /// Clear any currently cached rule evaluation data, restoring this
    /// object to its default constructed state (empty).
    void clear();

    /// Update, for the specified `sequenceNumber`, the cache for those
    /// rules indicated by the specified `relevantRulesMask` bit mask in the
    /// specified set of `rules`, by evaluating those rules for the
    /// specified `attributes`; return the bit mask indicating those rules
    /// that are known to be active.  If a bit in the returned bit mask
    /// value is set to 1, the rule at the corresponding index in `rules` is
    /// "active"; however, if a bit is set to 0, the corresponding rule is
    /// either not active *or* has not been evaluated.  This operation does,
    /// however, guarantee that all the rules indicated by the
    /// `relevantRulesMask` *will* be evaluated.  A particular rule is
    /// considered "active" if all of its attributes are satisfied by
    /// `attributes` (i.e., if `Rule::evaluate` returns `true` for
    /// `attributes`).  The behavior is undefined unless `rules` is not
    /// modified during this operation (i.e., any lock associated with
    /// `rules` must be locked during this operation).
    RuleSet::MaskType update(bsls::Types::Int64            sequenceNumber,
                             RuleSet::MaskType             relevantRulesMask,
                             const RuleSet&                rules,
                             const AttributeContainerList& attributes);

    // ACCESSORS

    /// Return `true` if this cache contains up-to-date cached rule
    /// evaluations having the specified `sequenceNumber` for the set of
    /// rules indicated by the specified `relevantRulesMask` bit mask, and
    /// `false` otherwise.
    bool isDataAvailable(bsls::Types::Int64 sequenceNumber,
                         RuleSet::MaskType  relevantRulesMask) const;

    /// Return a bit mask indicating those rules, from the set of rules
    /// provided in the last call to `update`, that are known to be active
    /// (as of that last call to `update`).  If a bit in the returned value
    /// is set to 1, the rule at the corresponding index is active; however,
    /// if a bit is set to 0, the corresponding rule is either not active
    /// *or* has not been evaluated.  Note that `isDataAvailable` should be
    /// called to test if this cache contains up-to-date evaluated rule
    /// information for the rules in which they are interested before using
    /// the result of this method.
    RuleSet::MaskType knownActiveRules() 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.
    bsl::ostream& print(bsl::ostream& stream,
                        int           level = 0,
                        int           spacesPerLevel = 4) const;
};

// FREE OPERATORS

/// Write a description of the data members of the specified `cache` to the
/// specified `stream` in some single-line human readable format, and return
/// the modifiable `stream`.
bsl::ostream& operator<<(
                     bsl::ostream&                               stream,
                     const AttributeContext_RuleEvaluationCache& cache);

                        // ======================
                        // class AttributeContext
                        // ======================

/// This class provides a mechanism for associating attributes with the
/// current thread, and evaluating the logging rules associated with a
/// category using those stored attributes.  `AttributeContext` contains
/// class data members that must be initialized (using the `initialize`
/// class method) with a `CategoryManager` object containing a `RuleSet`
/// that represents the currently installed logging rules for the process.
/// Clients can obtain the context for the current thread by calling the
/// `getContext` class method.  The `addAttributes` and `removeAttributes`
/// methods are used to add and remove collections of attributes from the
/// (thread-local) context object.  Finally, `AttributeContext` provides
/// methods (used primarily by other components in the `ball` package') to
/// determine the effect of the current logging rules on the logging
/// thresholds of a category.  The `hasRelevantActiveRules` method returns
/// `true` if there are any relevant and active rules that might modify the
/// logging thresholds of the supplied category.  A rule is "relevant" if
/// the rule's pattern matches the category's name, and a rule is "active"
/// if all the attributes defined for the rule are satisfied by the current
/// thread's attributes (i.e., `Rule::evaluate` returns `true` for the
/// collection of attributes maintained for the current thread by the
/// thread's `AttributeContext` object).  The `determineThresholdLevels`
/// method returns the logging threshold levels for a category, factoring in
/// any active rules that apply to the category that might override the
/// category's thresholds.  The behavior for the `hasRelevantActiveRules`
/// and `determineThresholdLevels` methods is undefined unless `initialize`
/// has been called.  Note that, in practice, `initialize` is called
/// *internally* when the logger manager singleton is initialized; clients
/// ordinarily should not call `initialize` directly.
class AttributeContext {

    // PRIVATE TYPES
    typedef AttributeContext_RuleEvaluationCache RuleEvaluationCache;

    // CLASS DATA
    static CategoryManager  *s_categoryManager_p;  // holds the rule set, rule
                                                   // set sequence number, and
                                                   // rule set mutex

    static bslma::Allocator *s_globalAllocator_p;  // allocator for thread-
                                                   // local context objects

    // DATA
    AttributeContainerList   d_containerList;      // list of attribute
                                                   // containers

    mutable RuleEvaluationCache
                             d_ruleCache_p;        // cache of rule evaluations

    bslma::Allocator        *d_allocator_p;        // allocator used to create
                                                   // this object (held, not
                                                   // owned)

    // FRIENDS
    friend class AttributeContextProctor;

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

    // PRIVATE CLASS METHODS

    /// Return a `const` reference to the singleton key for the thread-local
    /// storage in which the `AttributeContext` object is stored.  This
    /// method creates the key on the first invocation; all subsequent
    /// invocations return the key created on the initial call.  Note that
    /// it is more efficient to cache the return value of this method than
    /// to invoke it repeatedly.
    static const bslmt::ThreadUtil::Key& contextKey();

    /// Destroy the `AttributeContext` object pointed to by the specified
    /// `arg`.  Note that this function is intended to be called by the
    /// thread-specific storage facility when a thread exits.
    static void removeContext(void *arg);

    // PRIVATE CREATORS

    /// Create an `AttributeContext` object initially having no attributes.
    /// Optionally specify a `globalAllocator` used to supply memory.  If
    /// `globalAllocator` is 0, the currently installed global allocator is
    /// used.  Note that the `getContext` class method must be used to
    /// obtain the address of the attribute context for the current thread.
    AttributeContext(bslma::Allocator *globalAllocator = 0);

    /// Destroy this object.
    ~AttributeContext();

  public:
    // PUBLIC TYPES
    typedef AttributeContainerList::iterator iterator;

    // CLASS METHODS

    /// Return the address of the current thread's attribute context, if
    /// such context exists; otherwise, create an attribute context, install
    /// it in thread-local storage, and return the address of the newly
    /// created context.  Note that this method can be invoked safely even
    /// if the `initialize` class method has not yet been called.
    static AttributeContext *getContext();

    /// Initialize the static data members of `AttributeContext` using the
    /// specified `categoryManager`.  Optionally specify a `globalAllocator`
    /// used to supply memory.  If `globalAllocator` is 0, the currently
    /// installed global allocator is used.  Unless `reset` is subsequently
    /// called, invoking this method more than once will log an error
    /// message using `bsls::Log::platformDefaultMessageHandler`, but will
    /// have no other effect.  Note that in practice this method will be
    /// called *automatically* when the `LoggerManager` singleton is
    /// initialized -- i.e., it is not intended to be called directly by
    /// clients of the `ball` package.
    static void initialize(CategoryManager  *categoryManager,
                           bslma::Allocator *globalAllocator = 0);

    /// Return the address of the modifiable `AttributeContext` object
    /// installed in local storage for the current thread, or 0 if no
    /// attribute context has been created for this thread.  Note that this
    /// method can be invoked safely even if the `initialize` class method
    /// has not yet been called.
    static AttributeContext *lookupContext();

    /// Reset the static data members of `AttributeContext` to their initial
    /// state (0).  Unless `initialize` is subsequently called, invoking
    /// this method more than once has no effect.  Note that in practice
    /// this method will be called *automatically* when the `LoggerManager`
    /// singleton is destroyed -- i.e., it is not intended to be called
    /// directly by clients of the `ball` package.
    static void reset();

    /// Invoke the specified `visitor` for all attributes in all attribute
    /// containers maintained by this object.
    static void visitAttributes(
                   const bsl::function<void(const ball::Attribute&)>& visitor);

    // MANIPULATORS

    /// Add the specified `attributes` container to the list of attribute
    /// containers maintained by this object.  The behavior is undefined
    /// unless `attributes` remains valid *and* *unmodified* until either
    /// `attributes` is removed from this context, `clearCache` is called,
    /// or this object is destroyed.  Note that this method can be invoked
    /// safely even if the `initialize` class method has not yet been
    /// called.
    iterator addAttributes(const AttributeContainer *attributes);

    /// Clear this object's cache of evaluated rules.  Note that this method
    /// must be called if an `AttributeContainer` object supplied to
    /// `addAttributes` is modified outside of this context.
    void clearCache();

    /// Remove the specified `element` from the list of attribute containers
    /// maintained by this object.  Note that this method can be invoked
    /// safely even if the `initialize` class method has not yet been
    /// called.
    void removeAttributes(iterator element);

    // ACCESSORS

    /// Return `true` if there is at least one rule defined for this process
    /// that is both "relevant" to the specified `category` and "active",
    /// and `false` otherwise.  A rule is "relevant" to `category` if the
    /// rule's pattern matches `category->categoryName()`, and a rule is
    /// "active" if all the attributes defined for that rule are satisfied
    /// by the current thread's attributes (i.e., `Rule::evaluate` returns
    /// `true` for the collection of attributes maintained by this object).
    /// This method operates on the set of rules maintained by the category
    /// manager supplied to the `initialize` class method (which, in
    /// practice, should be the global set of rules for the process).  The
    /// behavior is undefined unless `initialize` has previously been
    /// invoked without a subsequent call to `reset`, and `category` is
    /// contained in the registry maintained by the category manager
    /// supplied to `initialize`.
    bool hasRelevantActiveRules(const Category *category) const;

    /// Populate the specified `levels` with the threshold levels for the
    /// specified `category`.  This method compares the threshold levels
    /// defined by `category` with those of any active rules that apply to
    /// that category, and determines the minimum severity (i.e., the
    /// maximum numerical value) for each respective threshold amongst those
    /// values.  A rule applies to `category` if the rule's pattern matches
    /// `category->categoryName()`, and a rule is active if all the
    /// attributes defined for that rule are satisfied by the current
    /// thread's attributes (i.e., `Rule::evaluate` returns `true` for the
    /// collection of attributes maintained by this object).  This method
    /// operates on the set of rules maintained by the category manager
    /// supplied to the `initialize` class method (which, in practice,
    /// should be the global set of rules for the process).  The behavior is
    /// undefined unless `initialize` has previously been invoked without a
    /// subsequent call to `reset`, and `category` is contained in the
    /// registry maintained by the category manager supplied to
    /// `initialize`.
    void determineThresholdLevels(ThresholdAggregate *levels,
                                  const Category     *category) const;

    /// Return `true` if an attribute having the specified `value` exists in
    /// any of the attribute containers maintained by this object, and
    /// `false` otherwise.  Note that this method can be invoked safely even
    /// if the `initialize` class method has not yet been called.
    bool hasAttribute(const Attribute& value) const;

    /// Return a `const` reference to the list of attribute containers
    /// maintained by this object.  Note that this method can be invoked
    /// safely even if the `initialize` class method has not yet been
    /// called.
    const AttributeContainerList& containers() 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.
    bsl::ostream& print(bsl::ostream& stream,
                        int           level = 0,
                        int           spacesPerLevel = 4) const;
};

// FREE OPERATORS

/// Write a description of the data members of the specified `context` to
/// the specified `stream` in a single-line human readable format, and
/// return a reference to the modifiable `stream`.
bsl::ostream& operator<<(bsl::ostream&           stream,
                         const AttributeContext& context);

                     // =============================
                     // class AttributeContextProctor
                     // =============================

/// This class implements a proctor that, on its own destruction, will destroy
/// the attribute context of the current thread.  Attribute contexts are stored
/// in thread-local memory.  On destruction, objects of this type will
/// deallocate the current thread's attribute context (if one has been
/// created), and set the thread-local storage pointer to 0.
class AttributeContextProctor {

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

  public:
    // CREATORS

    /// Create an `AttributeContextProctor` object that will destroy the
    /// current attribute context on destruction.
    explicit AttributeContextProctor();

    /// Destroy this object (as well as the current attribute context).
    ~AttributeContextProctor();
};

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

               // ------------------------------------------
               // class AttributeContext_RuleEvaluationCache
               // ------------------------------------------

// CREATORS
inline
AttributeContext_RuleEvaluationCache::AttributeContext_RuleEvaluationCache()
: d_evalMask(0)
, d_resultMask(0)
, d_sequenceNumber(-1)
{
}

// MANIPULATORS
inline
void AttributeContext_RuleEvaluationCache::clear()
{
    d_evalMask       =  0;
    d_resultMask     =  0;
    d_sequenceNumber = -1;
}

// ACCESSORS
inline
bool AttributeContext_RuleEvaluationCache::isDataAvailable(
                                    bsls::Types::Int64 sequenceNumber,
                                    RuleSet::MaskType  relevantRulesMask) const
{
    return sequenceNumber    == d_sequenceNumber
        && relevantRulesMask == (relevantRulesMask & d_evalMask);
}

inline
RuleSet::MaskType
AttributeContext_RuleEvaluationCache::knownActiveRules() const
{
    return d_resultMask;
}

                        // ----------------------
                        // class AttributeContext
                        // ----------------------

// MANIPULATORS
inline
AttributeContext::iterator
AttributeContext::addAttributes(const AttributeContainer *attributes)
{
    BSLS_ASSERT(attributes);

    d_ruleCache_p.clear();
    return d_containerList.pushFront(attributes);
}

inline
void AttributeContext::clearCache()
{
    d_ruleCache_p.clear();
}

inline
void AttributeContext::removeAttributes(iterator element)
{
    d_ruleCache_p.clear();
    d_containerList.remove(element);
}

// ACCESSORS
inline
const AttributeContainerList& AttributeContext::containers() const
{
    return d_containerList;
}

inline
bool AttributeContext::hasAttribute(const Attribute& value) const
{
    return d_containerList.hasValue(value);
}

                        // -----------------------------
                        // class AttributeContextProctor
                        // -----------------------------

// CREATORS
inline
AttributeContextProctor::AttributeContextProctor()
{
}

}  // close package namespace

// FREE OPERATORS
inline
bsl::ostream& ball::operator<<(
                            bsl::ostream&                               stream,
                            const AttributeContext_RuleEvaluationCache& cache)
{
    return cache.print(stream, 0, -1);
}

inline
bsl::ostream& ball::operator<<(bsl::ostream&           stream,
                               const AttributeContext& context)
{
    return context.print(stream, 0, -1);
}

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