// baljsn_simpleformatter.h                                           -*-C++-*-
#ifndef INCLUDED_BALJSN_SIMPLEFORMATTER
#define INCLUDED_BALJSN_SIMPLEFORMATTER

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

//@PURPOSE: Provide a simple formatter for encoding data in the JSON format.
//
//@CLASSES:
// baljsn::SimpleFormatter: a mechanism to encode data into JSON
//
//@SEE_ALSO: baljsn_encoder, baljsn_formatter, baljsn_printutil
//
//@DESCRIPTION: This component provides a class, 'baljsn::SimpleFormatter', for
// rendering JSON conforming text for objects, arrays, and various scalar
// types.
//
// This component provides an interface that is easier to use, and renders more
// readable "pretty" JSON, than 'baljsn::Formatter'.  Clients are encouraged to
// use 'baljsn::SimpleFormatter' instead of 'baljsn::Formatter' (see
// {Comparison to 'baljsn::Formatter'}).
//
// The 'SimpleFormatter' 'class' also provides the ability to specify
// formatting options at construction.  The options that can be provided
// include the encoding style (compact or pretty), the initial indentation
// level and spaces per level if encoding in the pretty format.
//
///Comparison to 'baljsn::Formatter'
///---------------------------------
//
///API Comparison
/// - - - - - - -
// Here is the side-by-side sequence of calls to create the following JSON
// using both components, assuming an existing stream 'os':
//..
//  {
//    "Object" : {
//      "Field 1" : 1,
//      "Field 2" : null
//    },
//    "Array" : [
//      1,
//      "string",
//      [],
//      [
//        [
//          {
//          }
//        ]
//      ]
//    ],
//    "True" : true
//  }
//..
// Some extra indentation has been added in these examples to show the various
// 'open'/'close' call nesting levels.
//..
//           Formatter                |             SimpleFormatter
// -----------------------------------+----------------------------------------
// baljsn::Formatter f(os);           | baljsn::SimpleFormatter sf(os);
//                                    |
// f.openObject();                    | sf.openObject();
//                                    |
//  f.openMember("Object");           |  sf.openObject("Object");
//   f.openObject();                  |   sf.addValue("Field 1", 1);
//    f.openMember("Field 1");        |   sf.addNullValue("Field 2");
//     f.putValue(1);                 |  sf.closeObject();
//    f.closeMember();                |
//    f.openMember("Field 2");        |  sf.openArray("Array");
//     f.putNullValue();              |   sf.addValue(1);        // No name
//    // Must remember NOT to call    |   sf.addValue("string"); // No name
//    // closeMember here!            |   sf.openArray(e_EMPTY_ARRAY_FORMAT);
//   f.closeObject();                 |   sf.closeArray(e_EMPTY_ARRAY_FORMAT);
//  f.closeMember();                  |   sf.openArray();
//                                    |    sf.openArray();
//  f.openMember("Array");            |     sf.openObject();
//   f.openArray();                   |     sf.closeObject();
//    f.putValue(1);                  |    sf.closeArray();
//    f.addArrayElementSeparator();   |   sf.closeArray();
//    f.putValue("string");           |  sf.closeArray();
//    f.addArrayElementSeparator();   |
//    f.openArray(true);              |  sf.addValue("True", true);
//    f.closeArray(true);             | sf.closeObject();
//    f.addArrayElementSeparator();   |
//    f.openArray();                  |
//     f.openArray();                 |
//      f.openObject();               |
//      f.closeObject();              |
//     f.closeArray();                |
//    f.closeArray();                 |
//                                    |
//    // Must remember NOT to call    |
//    // addArrayElementSeparator     |
//    // here!                        |
//   f.closeArray();                  |
//  f.closeMember();                  |
//                                    |
//  f.openMember("True");             |
//   f.putValue(true);                |
//  // Must remember NOT to call      |
//  // closeMember here!              |
//                                    |
// f.closeObject();                   |
// -----------------------------------+----------------------------------------
//
//..
//
// JSON Format
// -----------
// The JSON encoding format (see http://json.org or ECMA-404 standard for more
// information) specifies a self-describing and simple syntax that is built on
// two structures:
//
//: o Objects: JSON objects are represented as collections of name value
//:   pairs.  The 'SimpleFormatter' 'class' allows encoding objects by
//:   providing the 'openObject' and 'closeObject' methods to open and close an
//:   object and overloads for 'openObject', 'openArray', 'addValue' and
//:   'addNullValue' which take a 'name' to specify the named fields in the
//:   object, or the use of the 'addMemberName' manipulator followed by the
//:   overloads of 'openObject', 'openArray', 'addValue', and 'addNullValue'
//:   which do not take a name.
//:
//: o Arrays: JSON arrays are specified as an ordered list of values.  The
//:   'SimpleFormatter' 'class' provides the 'openArray' and 'closeArray'
//:   method to open and close an array, as well as overloads for 'openObject',
//:   'openArray', 'addValue' and 'addNullValue' which do not take a 'name' for
//:   array elements.
//
// The 'SimpleFormatter' 'class' provides the ability to specify formatting
// options at construction.  The options that can be provided include the
// encoding style (compact or pretty), the initial indentation level and spaces
// per level if encoding in the pretty format.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Encoding a Stock Portfolio in JSON
///- - - - - - - - - - - - - - - - - - - - - - -
// Let us suppose we have to encode a JSON document containing information
// about a small portfolio of stocks.  The eventual data we want to encode is
// represented by the following JSON string (which is the expected output of
// the encoding process):
//
// First, we specify the result that we are expecting to get:
//..
//{
//  const bsl::string EXPECTED = R"JSON({
//    "Stocks" : [
//      {
//        "Name" : "International Business Machines Corp",
//        "Ticker" : "IBM US Equity",
//        "Last Price" : 149.3,
//        "Dividend Yield" : 3.95
//      },
//      {
//        "Name" : "Apple Inc",
//        "Ticker" : "AAPL US Equity",
//        "Last Price" : 205.8,
//        "Dividend Yield" : 1.4
//      }
//    ]
//  })JSON";
//..
// Then, to encode this JSON document we create a 'baljsn::SimpleFormatter'
// object.  Since we want the document to be written in a pretty, easy to
// understand format we will specify 'true' for the 'usePrettyStyle' option and
// provide an appropriate initial indent level and spaces per level values:
//..
//  bsl::ostringstream      os;
//  baljsn::EncoderOptions  encoderOptions;
//
//  encoderOptions.setEncodingStyle(baljsn::EncoderOptions::e_PRETTY);
//  encoderOptions.setSpacesPerLevel(2);
//
//  baljsn::SimpleFormatter formatter(os, encoderOptions);
//..
// Next, we encode the start of the top level object, and open the first member
// "Stocks" (which holds an array of stock information):
//..
//  formatter.openObject();
//  formatter.openArray("Stocks");
//..
// Next, we render each element within the array of "Stocks" as an object that
// contains information for an individual stock:
//..
//  formatter.openObject();
//..
// We now encode the other elements in the stock object.
//..
//  formatter.addValue("Name", "International Business Machines Corp");
//  formatter.addValue("Ticker", "IBM US Equity");
//  formatter.addValue("Last Price", 149.3);
//  formatter.addValue("Dividend Yield", 3.95);
//..
// Then, close the first stock object.
//..
//  formatter.closeObject();
//..
// Next, we add another stock object.
//..
//  formatter.openObject();
//
//  formatter.addValue("Name", "Apple Inc");
//  formatter.addValue("Ticker", "AAPL US Equity");
//  formatter.addValue("Last Price", 205.8);
//  formatter.addValue("Dividend Yield", 1.4);
//
//  formatter.closeObject();
//..
// Similarly, we can continue to format the rest of the document.  For the
// purpose of this usage example we will complete this document.
//..
//  formatter.closeArray();
//  formatter.closeObject();
//..
// Once the formatting is complete the written data can be viewed from the
// stream passed to the formatter at construction.
//..
//  if (verbose)
//      bsl::cout << os.str() << bsl::endl;
//..
// Finally, verify the received result:
//..
//  assert(EXPECTED == os.str());
//}
//..
//
///Example 2: Encoding an array
///- - - - - - - - - - - - - - -
// Let us say we want to encode an array of various values.
//
// First, we create our 'formatter' as we did above:
//..
//{
//  bsl::ostringstream      os;
//  baljsn::EncoderOptions  encoderOptions;
//
//  encoderOptions.setEncodingStyle(baljsn::EncoderOptions::e_PRETTY);
//  encoderOptions.setSpacesPerLevel(2);
//
//  baljsn::SimpleFormatter formatter(os, encoderOptions);
//..
// Then we open our array.
//..
//  formatter.openArray();
//..
// Next, we populate the array with a series of unnamed values.  Named values
// are only used in objects, not arrays.
//..
//  formatter.addValue("First value");
//  formatter.addValue(2);
//  formatter.addValue(3);
//..
// Then, we demonstrate that arrays can be nested, opening another level of
// array, populating it, and closing it:
//..
//  formatter.openArray();
//  formatter.addValue("First value of inner array");
//  formatter.addValue(3.14159);
//  formatter.closeArray();
//..
// Arrays can also contain (unnamed) objects:
//..
//  formatter.openObject();
//..
// Next, we add (named) values to our object:
//..
//  formatter.addValue("Greeting", "Hello from the first inner object");
//  formatter.addValue("PI approximation", 3.14);
//  // We could, similarly, add nested named objects and/or named arrays
//..
// Then we close the nested object:
//..
//  formatter.closeObject();
//..
// Finally, we close the outer array:
//..
//  formatter.closeArray();
//}
//..

#include <balscm_version.h>

#include <baljsn_encoderoptions.h>
#include <baljsn_printutil.h>

#include <bdlb_print.h>

#include <bdlc_bitarray.h>

#include <bsl_ostream.h>

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

#include <bslmf_nestedtraitdeclaration.h>

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

#include <bsl_string_view.h>

namespace BloombergLP {
namespace baljsn {

class EncoderOptions;

                           // =====================
                           // class SimpleFormatter
                           // =====================

class SimpleFormatter {
    // This class implements a formatter providing operations for rendering
    // JSON text elements to an output stream (supplied at construction)
    // according to a set of formatting options (also supplied at
    // construction).
    //
    // This class has an interface that's easier to use than that of
    // 'baljsn::Formatter', and generates more correctly-formatted 'pretty'
    // output.

  public:
    // TYPES
    enum ArrayFormattingStyle {
        // This 'enum' lists all possible array formatting styles.
        e_REGULAR_ARRAY_FORMAT = 1,
        e_EMPTY_ARRAY_FORMAT
    };

  private:
    // DATA
    bsl::ostream&     d_outputStream;        // stream for output (held, not
                                             // owned)

    bool              d_useComma;            // whether next start item
                                             // ('add*', 'open*') needs a
                                             // preceding comma

    bool              d_started;             // whether we've formatted at
                                             // least one element

    bool              d_memberNameSupplied;  // whether the previous output
                                             // operation was 'addMemberName'

    bdlc::BitArray    d_callSequence;        // array specifying the sequence
                                             // in which the 'openObject' and
                                             // 'openArray' methods were
                                             // called.  An 'openObject' call
                                             // is represented by 'false' and
                                             // an 'openArray' call by 'true'.

    EncoderOptions    d_encoderOptions;      // formatting and encoding
                                             // options

    int               d_indentLevel;         // current indent level

    // PRIVATE MANIPULATORS
    void indent();
        // Unconditionally print onto the stream supplied at construction the
        // sequence of whitespace characters for the proper indentation of an
        // element at the current indentation level.  Note that this method
        // does not check that 'usePrettyStyle()' is 'true' before indenting.

    void printComma();
        // If 'd_useComma' is 'true', print a comma.  If 'usePrettyStyle()' is
        // also 'true', also print a newline.  This also sets
        // 'd_memberNameSupplied' to 'false'.

    void followWithComma(bool flag);
        // Set 'd_useComma' to the value of the specified 'flag', indicating
        // whether the next 'printComma()' call should actually print a comma.

    void printName(const bsl::string_view& name);
        // Print onto the stream supplied at construction the specified 'name',
        // followed by a ':'. The ':' is surrounded by a space on each side if
        // 'usePrettyStyle()' is 'true'.  It is the caller's responsibility to
        // call 'printComma()' - this routine does not handle commas, but does
        // call 'indent()' if necessary.

    // PRIVATE ACCESSORS
    bool usePrettyStyle() const;
        // Return 'true' if 'e_PRETTY == d_encoderOptions.encodingStyle()'.

    int spacesPerLevel() const;
        // Return 'd_encoderOptions.spacesPerLevel()'.

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

    // CREATORS
    explicit SimpleFormatter(bsl::ostream&          stream,
                             bslma::Allocator      *basicAllocator = 0);
    explicit SimpleFormatter(bsl::ostream&          stream,
                             const EncoderOptions&  encoderOptions,
                             bslma::Allocator      *basicAllocator = 0);
        // Create a 'SimpleFormatter' object using the specified 'stream'.
        // Optionally specify 'encoderOptions' to configure the output options
        // - if 'encoderOptions' is not supplied, a default-constructed
        // 'EncoderOptions' object will be used.  Note that the
        // 'encodeEmptyArrays' attribute in the 'encoderOptions' is ignored.
        // Optionally specify a 'basicAllocator' used to supply memory.  If
        // 'basicAllocator' is 0, the currently installed default allocator is
        // used.

    SimpleFormatter(const SimpleFormatter&  original,
                    bslma::Allocator       *basicAllocator);
        // Create a 'SimpleFormatter' object having the same value as the
        // specified 'original' object.  Optionally specify a 'basicAllocator'
        // used to supply memory.  If 'basicAllocator' is 0, the currently
        // installed default allocator is used.

    ~SimpleFormatter();
        // Destroy this object.  Note that correct JSON has been generated if
        // the 'isCompleteJSON()' call returns 'true'.

    // MANIPULATORS
    void openObject();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of an object (referred to as an
        // "object" in JSON), preceded, if necessary, by a comma.  The behavior
        // is undefined unless 'isNameNeeded()' is 'false'.

    void openObject(const bsl::string_view& name);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of an object (referred to as an
        // "object" in JSON) with the specified 'name' , preceded, if
        // necessary, by a comma.  The behavior is undefined unless
        // 'isNameNeeded()' is 'true'.

    void addMemberName(const bsl::string_view& name);
        // Print onto the stream supplied at construction the specified 'name'
        // in double-quotes, preceded, if necessary, by a comma, and followed
        // by a ':'.  The behavior is undefined unless 'isNameNeeded()' is
        // 'true'.  After this operation, 'isNameNeeded()' will be 'false', and
        // an immediately subsequent attempt to add a value (or open an object
        // or array) should not provide a name.

    void closeObject();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the end of an object (referred to as an
        // "object" in JSON).  The behavior is undefined unless
        // 'isNameNeeded()' is 'true'.

    void openArray(
                ArrayFormattingStyle formattingStyle = e_REGULAR_ARRAY_FORMAT);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of an array (referred to as an
        // "array" in JSON), preceded, if necessary, by a comma.  Optionally
        // specify 'formattingStyle' denoting if the array being opened should
        // be formatted as an empty array.  If 'formattingStyle' is not
        // specified then the array being opened is formatted as a regular
        // array having elements.    The behavior is undefined unless
        // 'isNameNeeded()' is 'false'.  Note that the formatting (and as a
        // consequence the 'formattingStyle') is relevant only if this
        // formatter encodes in the pretty style and is ignored otherwise.

    void openArray(
            const bsl::string_view& name,
            ArrayFormattingStyle    formattingStyle = e_REGULAR_ARRAY_FORMAT);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of an array (referred to as an
        // "array" in JSON) with the specified 'name', preceded, if necessary,
        // by a comma.  Optionally specify 'formattingStyle' denoting if the
        // array being opened should be formatted as an empty array.  If
        // 'formattingStyle' is not specified then the array being opened is
        // formatted as a regular array having elements.  The behavior is
        // undefined unless 'isNameNeeded()' is 'true'.  Note that the
        // formatting (and as a consequence the 'formattingStyle') is relevant
        // only if this formatter encodes in the pretty style and is ignored
        // otherwise.

    void closeArray(
                ArrayFormattingStyle formattingStyle = e_REGULAR_ARRAY_FORMAT);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the end of an array (referred to as an
        // "array" in JSON).  Optionally specify 'formattingStyle' denoting if
        // the array being closed should be formatted as an empty array.  If
        // 'formattingStyle' is not specified then the array being closed is
        // formatted as a regular array having elements.  The behavior is
        // undefined if 'isFormattingArray()' is 'false'.  Note that the
        // formatting (and as a consequence the 'formattingStyle') is relevant
        // only if this formatter encodes in the pretty style and is ignored
        // otherwise.

    void addNullValue();
        // Print onto the stream supplied at construction the value
        // corresponding to a null element, preceded, if necessary, by a comma.
        // The behavior is undefined unless 'isNameNeeded()' is 'false'.

    void addNullValue(const bsl::string_view& name);
        // Print onto the stream supplied at construction the value
        // corresponding to a null element with the specified 'name', preceded,
        // if necessary, by a comma.  The behavior is undefined unless
        // 'isNameNeeded()' is 'true'.

    template <class TYPE>
    int addValue(const TYPE& value);
        // Print onto the stream supplied at construction the specified
        // 'value', preceded, if necessary, by a comma, passing the optionally
        // specified 'options' through to the rendering routines.  Return 0 on
        // success and a non-zero value otherwise.  The behavior is undefined
        // unless 'isNameNeeded()' is 'false'.

    template <class TYPE>
    int addValue(const bsl::string_view& name, const TYPE& value);
        // Print onto the stream supplied at construction the specified 'name'
        // and the specified 'value', preceded, if necessary, by a comma,
        // passing the optionally specified 'options' through to the rendering
        // routines.  Return 0 on success and a non-zero value otherwise.  The
        // behavior is undefined unless 'isNameNeeded()' is 'true'.

    // ACCESSORS
    bool isCompleteJSON() const;
        // Return 'true' if this 'SimpleFormatter' has formatted a complete
        // JSON object, where all 'open*' calls have been balanced by their
        // corresponding 'close*' calls.  Note that a default-constructed
        // 'SimpleFormatter' will return 'false' - an empty string is not valid
        // JSON.

    bool isFormattingArray() const;
        // Return 'true' if this 'SimpleFormatter' is currently formatting an
        // array and 'false' otherwise.  It is formatting an array if the last
        // 'open*' method overload ('openArray' or 'openObject') called on this
        // 'SimpleFormatter' for which the corresponding 'close*' method
        // (respectively, 'closeArray' or 'closeObject') was 'openArray'.  If
        // 'isFormattingArray()' is 'true', then 'isFormattingObject()' is
        // 'false'.  Note that both can be 'false', at the 'top-level' initial
        // scope before anything is added/opened or after the first 'open*'
        // call has been closed'

    bool isFormattingObject() const;
        // Return 'true' if this 'SimpleFormatter' is currently formatting an
        // object scope  and 'false' otherwise.  It is formatting an object
        // scope if the last 'open*' method overload ('openArray' or
        // 'openObject') called on this 'SimpleFormatter' for which the
        // corresponding 'close*' method (respectively, 'closeArray' or
        // 'closeObject') was 'openObject'.  If 'isFormattingObject()' is
        // 'true', then 'isFormattingArray()' is 'false'.  Note that both can
        // be 'false', at the 'top-level' initial scope before anything is
        // added/opened or after the first 'open*' call has been closed'
        // JSON.

    bool isNameNeeded() const;
        // Return 'true' if a subsequent attempt to add a value must supply a
        // 'name', and 'false' otherwise.  This will be 'true' if
        // 'isFormattingObject()' is 'true', and 'addMemberName()' was not the
        // most recently called manipulator.  That is, a name is needed if this
        // formatter is currently in the context of formatting the members of a
        // JSON object, and 'addMemberName' has not been called to explicitly
        // provide a name for the next member.

    // Aspects

    bslma::Allocator *allocator() const;
        // Return the allocator used by this object to supply memory.  Note
        // that if no allocator was supplied at construction the currently
        // installed default allocator is used.
};

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

                        // ---------------
                        // class SimpleFormatter
                        // ---------------

// PRIVATE MANIPULATORS
inline
void SimpleFormatter::followWithComma(bool flag)
{
    d_useComma = flag;
}

inline
void SimpleFormatter::indent()
{
    bdlb::Print::indent(d_outputStream, d_indentLevel, spacesPerLevel());
}

inline
void SimpleFormatter::printComma()
{
    d_started = true;

    if (d_useComma) {
        d_outputStream << ',';

        if (usePrettyStyle()) {
            d_outputStream << '\n';
        }
    }

    d_memberNameSupplied = false;
}

inline
void SimpleFormatter::printName(const bsl::string_view& name)
{
    if (usePrettyStyle()) {
        indent();
    }

    const int rc = PrintUtil::printValue(d_outputStream, name);
    if (rc) {
        return;                                                       // RETURN
    }

    d_outputStream << (usePrettyStyle() ? " : " : ":");
}

// PRIVATE ACCESSORS
inline
int SimpleFormatter::spacesPerLevel() const
{
    return d_encoderOptions.spacesPerLevel();
}

inline
bool SimpleFormatter::usePrettyStyle() const
{
    return EncoderOptions::e_PRETTY == d_encoderOptions.encodingStyle();
}

// MANIPULATORS
inline
void SimpleFormatter::addMemberName(const bsl::string_view& name)
{
    BSLS_ASSERT(isNameNeeded());

    printComma();
    followWithComma(false);

    printName(name);

    d_memberNameSupplied = true;
}

inline
void SimpleFormatter::addNullValue()
{
    BSLS_ASSERT(!isNameNeeded());

    bool needIndent = usePrettyStyle() && !d_memberNameSupplied;

    printComma();
    followWithComma(true);

    if (needIndent) {
        indent();
    }

    d_outputStream << "null";
}

inline
void SimpleFormatter::addNullValue(const bsl::string_view& name)
{
    BSLS_ASSERT(isNameNeeded());

    printComma();
    followWithComma(true);

    printName(name);

    d_outputStream << "null";
}

template <class TYPE>
int SimpleFormatter::addValue(const TYPE& value)
{
    BSLS_ASSERT(!isNameNeeded());

    bool needIndent = usePrettyStyle() && !d_memberNameSupplied;

    printComma();
    followWithComma(true);

    if (needIndent) {
        indent();
    }

    return baljsn::PrintUtil::printValue(
        d_outputStream, value, &d_encoderOptions);
}

template <class TYPE>
int SimpleFormatter::addValue(const bsl::string_view& name, const TYPE& value)
{
    BSLS_ASSERT(isNameNeeded());

    printComma();
    followWithComma(true);

    printName(name);

    return baljsn::PrintUtil::printValue(
        d_outputStream, value, &d_encoderOptions);
}

// ACCESSORS
inline
bool SimpleFormatter::isCompleteJSON() const
{
    return d_started && (1 == d_callSequence.length());
}

inline
bool SimpleFormatter::isFormattingArray() const
{
    BSLS_ASSERT(d_callSequence.length() >= 1);

    return d_callSequence.length() > 1 &&
           d_callSequence[d_callSequence.length() - 1];
}

inline
bool SimpleFormatter::isFormattingObject() const
{
    BSLS_ASSERT(d_callSequence.length() >= 1);

    return d_callSequence.length() > 1 &&
           !d_callSequence[d_callSequence.length() - 1];
}

inline
bool SimpleFormatter::isNameNeeded() const
{
    return isFormattingObject() && !d_memberNameSupplied;
}

                                  // Aspects

inline
bslma::Allocator *SimpleFormatter::allocator() const
{
    return d_callSequence.allocator();
}

}  // close package namespace

}  // close enterprise namespace

#endif

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