// balxml_namespaceregistry.h                                         -*-C++-*-
#ifndef INCLUDED_BALXML_NAMESPACEREGISTRY
#define INCLUDED_BALXML_NAMESPACEREGISTRY

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

//@PURPOSE: Provide a unique integer ID for each XML namespace.
//
//@CLASSES:
//   balxml::NamespaceRegistry: namespace-to-id mapping registry
//
//@SEE_ALSO: balxml_prefixstack
//
//@DESCRIPTION: This component provides an in-core value-semantic type,
// `balxml::NamespaceRegistry`, that associates an integer ID with each
// registered namespace URI.  In typical usage, client code would call the
// `lookupOrRegister` method each time it encounters a namespace URI.  The
// `lookupOrRegister` method will return the ID corresponding to the URI,
// assigning a new ID if none already exists.  The client can also retrieve
// the ID an already-registered namespace by providing the URI to the `lookup`
// method and can retrieve the URI of an already-registered namespace by
// providing the ID to the `lookup` method.
//
// Note that namespace IDs may be negative.  Client code should not assume an
// incremental assignment of IDs starting at zero.  (See Preregistered
// Namespaces), below.
//
///Preregistered Namespaces
///------------------------
// Even before any namespaces have been registered, a
// `balxml::NamespaceRegistry` can be used to lookup several preregistered
// namespaces.  The IDs for these preregistered namespaces are declared as
// constants within the `balxml::NamespaceRegistry` class.  These constants and
// their associated URI's are as follows:
// ```
// Namespace ID               URI String
// ============               ==========
// BAEXML_XML                   http://www.w3.org/XML/1998/namespace
// BAEXML_XMLNS                 http://www.w3.org/2000/xmlns/
// BAEXML_XMLSCHEMA             http://www.w3.org/2001/XMLSchema
// BAEXML_XMLSCHEMA_INSTANCE    http://www.w3.org/2001/XMLSchema-instance
// BAEXML_WSDL                  http://schemas.xmlsoap.org/wsdl/
// BAEXML_WSDL_SOAP             http://schemas.xmlsoap.org/wsdl/soap/
// BAEXML_BDEM                  http://bloomberg.com/schemas/bdem
// ```
// Note that the above constants are negative numbers.  In addition, the
// value, -1, is permanently assigned to the empty string.  The use of
// predefined namespace IDs allows client code avoid lookups of the above,
// well-known URIs.
//
///Thread Safety
///-------------
// It is safe to read or modify multiple instances of
// `balxml::NamespaceRegistry` simultaneously, each from a separate thread.  It
// is safe to read a single instance of `balxml::NamespaceRegistry` from
// multiple threads, provided no thread is modifying it at the same time.  It
// is not safe to read or modify an instance of `balxml::NamespaceRegistry`
// from one thread while any other thread is modifying the same instance.
//
///Usage
///-----
// Typically, a program will register namespaces as it encounters them in an
// XML document.  Alternatively, namespaces that are important to the program
// are registered in advance, as in the following code:
// ```
// const char googleUri[] = "http://www.google.com/schemas/results.xsd";
// const char yahooUri[]  = "http://www.yahoo.com/xsd/searchResults.xsd";
//
// balxml::NamespaceRegistry namespaceRegistry;
// int googleId = namespaceRegistry.lookupOrRegister(googleUri);
// assert(googleId >= 0);
// int yahooId = namespaceRegistry.lookupOrRegister(yahooUri);
// assert(yahooId >= 0);
// assert(yahooId != googleId);
// ```
// Later, IDs can be looked up without concern for whether they have already
// been registered.  Any new namespaces are simply given a new ID:
// ```
// char input[100];
//
// // First input is a new namespace URI.
// bsl::strcpy(input, "http://www.bloomberg.com/schemas/example.xsd");
// int id1 = namespaceRegistry.lookupOrRegister(input);
// assert(id1 >= 0);
// assert(id1 != googleId);
// assert(id1 != yahooId);
//
// // Next input happens to be the same as yahoo.
// bsl::strcpy(input, "http://www.yahoo.com/xsd/searchResults.xsd");
// int id2 = namespaceRegistry.lookupOrRegister(input);
// assert(id2 == yahooId);
// ```
// If one of the preregistered namespaces is presented, it's predefined ID is
// returned, even though it was never explicitly registered:
// ```
// bsl::strcpy(input, "http://www.w3.org/2001/XMLSchema");
// int id3 = namespaceRegistry.lookupOrRegister(input);
// assert(id3 == balxml::NamespaceRegistry::BAEXML_XMLSCHEMA);
// ```
// Using the `lookup` method, a namespace ID can be looked up without
// registering it.  In this case, an unregistered namespace will result in an
// ID of -1:
// ```
// assert(googleId  == namespaceRegistry.lookup(googleUri));
// assert(balxml::NamespaceRegistry::BAEXML_BDEM ==
//        namespaceRegistry.lookup("http://bloomberg.com/schemas/bdem"));
// assert(-1 == namespaceRegistry.lookup("urn:1234"));
// ```
// There is also a `lookup` method for performing the reverse mapping -- from
// ID to URI:
// ```
// const char *uri = namespaceRegistry.lookup(googleId);
// assert(0 == bsl::strcmp(uri, googleUri));
// ```

#include <balscm_version.h>

#include <bslma_allocator.h>
#include <bslma_usesbslmaallocator.h>

#include <bslmf_nestedtraitdeclaration.h>

#include <bsl_string.h>
#include <bsl_vector.h>
#include <bsl_iosfwd.h>

namespace BloombergLP {

namespace balxml {
                          // =======================
                          // class NamespaceRegistry
                          // =======================

/// Mapping that associates a unique integer with each registered namespace
/// URI.
class NamespaceRegistry {

  private:
    // PRIVATE MEMBER VARIABLES
    bsl::vector<bsl::string> d_namespaces; // vector of namespaces, indexed by
                                           // the namespace ID.

    /// Must be a friend for engineering reasons.  Unlike most
    /// value-semantic types, there is no efficient way to read the entire
    /// value of a namespace registry object.
    friend inline
    bool operator==(const NamespaceRegistry& lhs,
                    const NamespaceRegistry& rhs);

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(NamespaceRegistry,
                                                    bslma::UsesBslmaAllocator);

    // PUBLIC TYPES
    enum {
        // Preregistered namespace IDs.
        e_NO_NAMESPACE = -1,    // (empty URI string)

        e_PREDEF_MIN = 0x40000000,

        e_XML = e_PREDEF_MIN, // http://www.w3.org/XML/1998/namespace
        e_XMLNS,              // http://www.w3.org/2000/xmlns/
        e_XMLSCHEMA,          // http://www.w3.org/2001/XMLSchema
        e_XMLSCHEMA_INSTANCE, // http://www.w3.org/2001/XMLSchema-instance
        e_WSDL,               // http://schemas.xmlsoap.org/wsdl/
        e_WSDL_SOAP,          // http://schemas.xmlsoap.org/wsdl/soap/
        e_BDEM,               // http://bloomberg.com/schemas/bdem

        BAEXML_PREDEF_MAX
#ifndef BDE_OMIT_INTERNAL_DEPRECATED
      , BAEXML_NO_NAMESPACE = e_NO_NAMESPACE
      , BAEXML_PREDEF_MIN = e_PREDEF_MIN
      , BAEXML_XML = e_XML
      , BAEXML_XMLNS = e_XMLNS
      , BAEXML_XMLSCHEMA = e_XMLSCHEMA
      , BAEXML_XMLSCHEMA_INSTANCE = e_XMLSCHEMA_INSTANCE
      , BAEXML_WSDL = e_WSDL
      , BAEXML_WSDL_SOAP = e_WSDL_SOAP
      , BAEXML_BDEM = e_BDEM
      , NSID_NO_NAMESPACE       = e_NO_NAMESPACE
      , NSID_PREDEF_MIN         = e_PREDEF_MIN
      , NSID_XML                = e_XML
      , NSID_XMLNS              = e_XMLNS
      , NSID_XMLSCHEMA          = e_XMLSCHEMA
      , NSID_XMLSCHEMA_INSTANCE = e_XMLSCHEMA_INSTANCE
      , NSID_WSDL               = e_WSDL
      , NSID_WSDL_SOAP          = e_WSDL_SOAP
      , NSID_BDEM               = e_BDEM
      , NSID_PREDEF_MAX         = BAEXML_PREDEF_MAX
#endif // BDE_OMIT_INTERNAL_DEPRECATED
    };

    // CREATORS

    /// Construct an empty registry.  Optionally specify a `basicAllocator`
    /// used to supply memory.  If `basicAllocator` is 0, the current
    /// default allocator is used.
    NamespaceRegistry(bslma::Allocator *basicAllocator = 0);

    /// Construct a copy of the specified `other` namespace registry using
    /// the (optionally) specified `basicAllocator`.  For a given URI, the
    /// results of calling `lookup` by URI will produce equal results for
    /// this object and for `other`.  For a given integer ID, the result of
    /// calling `lookup` by ID will produce different pointers that compare
    /// equal using `strcmp`.
    NamespaceRegistry(const NamespaceRegistry&  other,
                      bslma::Allocator         *basicAllocator=0);

    /// Destroy this object.  Release all memory to the allocator used at
    /// construction.
    ~NamespaceRegistry();

    // MANIPULATORS

    /// Discard the contents of this registry and assign it the contents of
    /// the specified `rhs` registry.  For a given URI, the results of
    /// calling `lookup` by URI will produce equal results for this object
    /// and for `rhs`.  For a given integer ID, the result of calling
    /// `lookup` by ID will produce different pointers that compare equal
    /// using `strcmp`.
    NamespaceRegistry& operator=(const NamespaceRegistry& rhs);

    /// Return the integer ID for the specified `namespaceUri`, assigning a
    /// new ID if the `namespaceUri` has not been registered before.  Note
    /// that the IDs for pre-registered namespaces (including the empty
    /// URI) are less than zero.  (See "Preregistered Namespaces" in the
    /// 'balxml_namespaceregistry component-level documentation.)
    int lookupOrRegister(const bsl::string_view& namespaceUri);

    /// Removes all registered namespaces.  Preregistered namespaces are
    /// not removed.
    void reset();

    // ACCESSORS

    /// Return the integer ID for the specified `namespaceUri` or -1 if the
    /// namespace has not been registered.  Note that not all negative
    /// return values correspond to unregistered namespaces.  Preregistered
    /// namespaces always have negative IDs.  (See "Preregistered
    /// Namespaces" in the `balxml_namespaceregistry` component-level
    /// documentation.)  Note that a return value of -1 can mean either an
    /// unregistered namespace or an empty URI string.  This dual-use of -1
    /// is deliberate and simplifies error handling in most clients.
    int lookup(const bsl::string_view& namespaceUri) const;

    /// Return the null-terminated string containing the URI of the
    /// namespace registered with the specified `id` or an empty (not null)
    /// string if `id` does not correspond to a preregistered namespace or
    /// a namespace that was previously registered with this object.
    const char *lookup(int id) const;

    /// Print the contents of this object to the specified `stream` in
    /// human-readable form.
    void print(bsl::ostream& stream) const;
};

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

// FREE OPERATORS

/// Return true if the specified `lhs` registry has the same value as the
/// specified `rhs` registry and false otherwise.  The two registries have
/// the same value if, for any possible URI string, `u`,
/// `lhs.lookup(u) == rhs.lookup(u)`.
inline
bool operator==(const NamespaceRegistry& lhs, const NamespaceRegistry& rhs);

/// Return true if the specified `lhs` registry does not have the same
/// value as the specified `rhs` registry and false otherwise.  The two
/// registries do not have the same value if there exists a URI string,
/// `u`, such that `lhs.lookup(u)` != `rhs.lookup(u)`.
inline
bool operator!=(const NamespaceRegistry& lhs, const NamespaceRegistry& rhs);

/// Print the contents of the specified `r` registry to the specified `os`
/// stream in human-readable form and return a modifiable reference to
/// `os`.
inline
bsl::ostream& operator<<(bsl::ostream& os, const NamespaceRegistry& r);

// CREATORS
inline
NamespaceRegistry::NamespaceRegistry(bslma::Allocator *basicAllocator)
: d_namespaces(basicAllocator)
{
}

inline
NamespaceRegistry::NamespaceRegistry(const NamespaceRegistry&  other,
                                     bslma::Allocator         *basicAllocator)
: d_namespaces(other.d_namespaces, basicAllocator)
{
}

inline
NamespaceRegistry::~NamespaceRegistry()
{
}

// MANIPULATORS
inline
NamespaceRegistry&
NamespaceRegistry::operator=(const NamespaceRegistry& rhs)
{
    d_namespaces = rhs.d_namespaces;
    return *this;
}

inline
void NamespaceRegistry::reset()
{
    d_namespaces.clear();
}
}  // close package namespace

// FREE OPERATORS
inline
bool balxml::operator==(const NamespaceRegistry& lhs,
                        const NamespaceRegistry& rhs)
{
    return lhs.d_namespaces == rhs.d_namespaces;
}

inline
bool balxml::operator!=(const NamespaceRegistry& lhs,
                        const NamespaceRegistry& rhs)
{
    return ! (lhs == rhs);
}

inline
bsl::ostream& balxml::operator<<(bsl::ostream& os, const NamespaceRegistry& r)
{
    r.print(os);
    return os;
}

}  // close enterprise namespace

#endif // ! defined(INCLUDED_BAEXML_NAMESPACEREGISTRY)

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