// baltzo_zoneinfocache.h                                             -*-C++-*-
#ifndef INCLUDED_BALTZO_ZONEINFOCACHE
#define INCLUDED_BALTZO_ZONEINFOCACHE

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

//@PURPOSE: Provide a cache for time-zone information.
//
//@CLASSES:
//  baltzo::ZoneinfoCache: a cache for time-zone information
//
//@SEE_ALSO: baltzo_zoneinfo, baltzo_defaultzoneinfocache
//
//@DESCRIPTION: This component defines a class, 'baltzo::ZoneinfoCache', that
// serves as a cache of 'baltzo::Zoneinfo' objects.  A time-zone cache is
// supplied, on construction, with a 'baltzo::Loader' object to obtain
// time-zone information objects from some data source.  Invocation of the
// 'getZoneinfo' method for a specified time zone returns the address of either
// previously cached data, or if that data is not cache-resident, a new loaded
// 'baltzo::Zoneinfo' object, which is cached for use in subsequent calls to
// 'getZoneinfo' and 'lookupZoneinfo'.  Addresses returned by either of these
// methods are valid for the lifetime of the cache.
//
///Thread Safety
///-------------
// 'baltzo::ZoneinfoCache' is fully *thread-safe*, meaning that all non-creator
// operations on an object can be safely invoked simultaneously from multiple
// threads.
//
///Usage
///-----
// In this section, we demonstrate creating a 'baltzo::ZoneinfoCache' object
// and using it to access time zone information.
//
///Example 1: Creating a Concrete 'baltzo::Loader'
///- - - - - - - - - - - - - - - - - - - - - - - -
// A 'baltzo::ZoneinfoCache' object is provided a 'baltzo::Loader' on
// construction.  The 'loader' is used to populate the cache per user requests
// via the 'getZoneinfo' method.  In this example, we use a 'TestLoader'
// implementation of the 'baltzo::Loader' protocol, based on the
// 'baltzo_testloader' component.  In this example, our test loader is
// explicitly primed with responses for requests for certain time-zone
// identifiers.  Note that, in practice, a 'loader' typically obtains time-zone
// information from some external data store (e.g., see
// 'baltzo_datafileloader').
//
// We start by creating and initializing a couple of example time zone
// information objects.  Note that the 'baltzo::Zoneinfo' objects below are
// illustrative, and contain no actual time zone information:
//..
//  baltzo::Zoneinfo newYorkZoneinfo;
//  newYorkZoneinfo.setIdentifier("America/New_York");
//
//  baltzo::Zoneinfo londonZoneinfo;
//  londonZoneinfo.setIdentifier("Europe/London");
//..
// Next we create a description of Eastern Standard Time (EST) and Greenwich
// Mean Time (GMT):
//..
//  baltzo::LocalTimeDescriptor est(-5 * 60 * 60, false, "EST");
//  baltzo::LocalTimeDescriptor gmt(           0, false, "GMT");
//..
// Then we set the initial transition for 'newYorkZoneinfo' to Eastern Standard
// Time, and the initial transition for 'londonZoneinfo' to Greenwich Mean
// Time.  Note that such an initial transition is required for a
// 'baltzo::Zoneinfo' object to be considered Well-Formed (see 'isWellFormed'):
//..
//  bsls::Types::Int64 firstTime = bdlt::EpochUtil::convertToTimeT64(
//                                                    bdlt::Datetime(1, 1, 1));
//  newYorkZoneinfo.addTransition(firstTime, est);
//  londonZoneinfo.addTransition(firstTime, gmt);
//..
// Next we create a 'TestLoader', and then populate it with our example time
// zone information objects:
//..
//  TestLoader testLoader;
//  testLoader.setTimeZone(newYorkZoneinfo);
//  testLoader.setTimeZone(londonZoneinfo);
//..
// Finally, we verify that 'testLoader' contains the configured
// 'baltzo::Zoneinfo' objects for New York and London:
//..
//  baltzo::Zoneinfo newYorkResult;
//  int rc = testLoader.loadTimeZone(&newYorkResult, "America/New_York");
//  assert(0 == rc);
//  assert(newYorkZoneinfo == newYorkResult);
//
//  baltzo::Zoneinfo londonResult;
//  rc = testLoader.loadTimeZone(&londonResult, "Europe/London");
//  assert(0 == rc);
//  assert(londonZoneinfo == londonResult);
//..
//
///Example 2: Creating and Using a 'baltzo::ZoneinfoCache'
///- - - - - - - - - - - - - - - - - - - - - - - - - - - -
// In this example, we create a 'baltzo::ZoneinfoCache', and use it to access
// time zone information for several time zones.
//
// We start by creating a 'baltzo::ZoneinfoCache' object supplied with the
// address of the 'TestLoader' we populated in the preceding example:
//..
//  baltzo::ZoneinfoCache cache(&testLoader);
//..
// Next, we verify the newly constructed cache does not contain either New York
// or London:
//..
//  assert(0 == cache.lookupZoneinfo("America/New_York"));
//  assert(0 == cache.lookupZoneinfo("Europe/London"));
//..
// Then, we call 'getZoneinfo' to obtain the data for the New York time zone.
// Note that, because this is the first 'getZoneinfo' operation on the class,
// the time-zone data has not previously been retrieved, and the data must be
// loaded using the loader supplied at construction:
//..
//  const baltzo::Zoneinfo *newYork = cache.getZoneinfo(&rc,
//                                                      "America/New_York");
//
//  assert(0 == rc);
//  assert(0 != newYork);
//  assert("America/New_York" == newYork->identifier());
//..
// Next, we verify that a subsequent call 'lookupZoneinfo' for New York,
// returns the previously cached value.  However, a call to 'lookupZoneinfo'
// for London will return 0 because the value has not been cached:
//..
//  assert(newYork == cache.lookupZoneinfo("America/New_York"));
//  assert(0       == cache.lookupZoneinfo("Europe/London"));
//..
// Next, we call 'getZoneinfo' for London and verify that it returns the
// expected value:
//..
//  const baltzo::Zoneinfo *london = cache.getZoneinfo(&rc, "Europe/London");
//  assert(0 == rc);
//  assert(0 != london);
//  assert("Europe/London" == london->identifier());
//..
// Finally, we call 'getZoneinfo' with time zone identifier unknown to our
// 'TestLoader'.  The call to 'getZoneinfo' returns 0 because the time zone
// information cannot be loaded.  Examination of 'rc' shows indicates that the
// identifier is not supported:
//..
//  assert(0 == cache.getZoneinfo(&rc, "badId"));
//  assert(baltzo::ErrorCode::k_UNSUPPORTED_ID == rc);
//..

#include <balscm_version.h>

#include <baltzo_loader.h>
#include <baltzo_zoneinfo.h>

#include <bdlb_cstringless.h>

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

#include <bslmf_nestedtraitdeclaration.h>

#include <bslmt_rwmutex.h>

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

#include <bsl_map.h>

namespace BloombergLP {
namespace baltzo {

                            // ===================
                            // class ZoneinfoCache
                            // ===================

class ZoneinfoCache {
    // This class provides an efficient mechanism for retrieving information
    // about a given time zone.  The first time a client requests information
    // for some time-zone identifier, that information is loaded (using the
    // 'Loader' supplied at construction), returned to the client (via a
    // 'const' pointer value), and cached for future use.  Subsequent requests
    // for the same time-zone return the cached information.  The returned
    // values are valid for the lifetime of this object.
    //
    // This class:
    //: o is *exception-neutral*
    //: o is *fully* *thread-safe*
    // For terminology see 'bsldoc_glossary'.

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

  private:
    // PRIVATE TYPES
    typedef bsl::map<const char *, Zoneinfo *, bdlb::CStringLess> ZoneinfoMap;

    // DATA
    ZoneinfoMap             d_cache;      // cached time-zone info, indexed by
                                          // time-zone id

    Loader                 *d_loader_p;   // loader used to obtain time-zone
                                          // information (held, not owned)

    mutable bslmt::RWMutex  d_lock;       // cache access synchronization

    allocator_type          d_allocator;  // allocator used to supply memory

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

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(ZoneinfoCache,
                                   bslma::UsesBslmaAllocator);
        // 'ZoneinfoCache' is allocator-aware.

    // CREATORS
    explicit ZoneinfoCache(
                          Loader                *loader,
                          const allocator_type&  allocator = allocator_type());
        // Create an empty cache of time-zone information that will use the
        // specified 'loader' to populate the cache, as-needed, with time zone
        // information.  Optionally specify an 'allocator' (e.g., the address
        // of a 'bslma::Allocator' object) to supply memory; otherwise, the
        // default allocator is used.  In order the populate the cache for a
        // time zone identifier, 'loader' must return a 'Zoneinfo' object that
        // is well-formed (see 'ZoneinfoUtil::isWellFormed') and whose
        // 'identifier' matches the supplied time zone identifier.

    ~ZoneinfoCache();
        // Destroy this object.

    // MANIPULATORS
    const Zoneinfo *getZoneinfo(const char *timeZoneId);
    const Zoneinfo *getZoneinfo(int *rc, const char *timeZoneId);
        // Return the address of the non-modifiable 'Zoneinfo' object
        // describing the time zone identified by the specified 'timeZoneId',
        // or 0 if the operation does not succeed.  If the information for
        // 'timeZoneId' has not been previously cached, then attempt to
        // populate this object using the 'loader' supplied at construction.
        // Optionally specify the address of an integer, 'rc', in which to load
        // the return code for this operation.  If 'rc' is specified, load 0
        // into 'rc' if the operation succeeds, 'ErrorCode::k_UNSUPPORTED_ID'
        // if the time-zone identifier is not supported, and a negative value
        // if the operation does not succeed for any other reason.  If the
        // returned address is non-zero, the Zoneinfo object returned is
        // guaranteed to be well-formed (i.e., 'ZoneinfoUtil::isWellFormed'
        // will return 'true' if called with the returned value), and remain
        // valid for the lifetime of this object.  The behavior is undefined if
        // 'rc' is 0.

    // ACCESSORS
    const Zoneinfo *lookupZoneinfo(const char *timeZoneId) const;
        // Return the address of the non-modifiable cached description of the
        // time zone identified by the specified 'timeZoneId', and 0 if
        // information for 'timeZoneId' has not previously been cached (by a
        // call to 'getZoneinfo').  If the returned address is non-zero, the
        // Zoneinfo object returned is guaranteed to be well-formed (i.e.,
        // 'ZoneinfoUtil::isWellFormed will return 'true' if called with the
        // returned value), and remain valid for the lifetime of this object.

    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.
};

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

                            // -------------------
                            // class ZoneinfoCache
                            // -------------------

// CREATORS
inline
ZoneinfoCache::ZoneinfoCache(Loader *loader, const allocator_type&  allocator)
: d_cache(allocator)
, d_loader_p(loader)
, d_allocator(allocator)
{
    BSLS_ASSERT(0 != loader);
}

// MANIPULATORS
inline
const Zoneinfo *ZoneinfoCache::getZoneinfo(const char *timeZoneId)
{
    BSLS_ASSERT(0 != timeZoneId);

    int rc;
    return getZoneinfo(&rc, timeZoneId);
}

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

}  // close package namespace
}  // 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 ----------------------------------