// baljsn_formatter.h                                                 -*-C++-*-
#ifndef INCLUDED_BALJSN_FORMATTER
#define INCLUDED_BALJSN_FORMATTER

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

//@PURPOSE: Provide a formatter for encoding data in the JSON format.
//
//@CLASSES:
// baljsn::Formatter: JSON formatter
//
//@SEE_ALSO: baljsn_encoder, baljsn_printutil, baljsn_simpleformatter
//
//@DESCRIPTION: This component provides a class, 'baljsn::Formatter', for
// formatting JSON objects, arrays, and name-value pairs in the JSON encoding
// format to a specified output stream.
//
// 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 'Formatter' 'class' allows encoding objects by providing the
//:   'openObject' and 'closeObject' methods to open and close an object and
//:   the 'openMember', 'closeMember', and 'putValue' methods to add members
//:   and values to an object.
//:
//: o Arrays: JSON arrays are specified as an ordered list of values.  The
//:   'Formatter' 'class' provides the 'openArray' and 'closeArray' method to
//:   open and close an array.  Additionally the 'Formatter' 'class' allows of
//:   separation of array items by a comma via the 'addArrayElementSeparator'
//:   method.
//
// The 'Formatter' '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.
//
// Valid sequence of operations
// - - - - - - - - - - - - - -
// The 'Formatter' 'class' does only minimal checking to verify that the
// sequence of operations called on its object result in a valid JSON
// document.  It is the user's responsibility to ensure that the methods
// provided by this component are called in the right order.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Encoding a Stock Portfolio in JSON
///- - - - - - - - - - - - - - - - - - - - - - -
// Let us say that we have to encode a JSON document with the following
// information about stocks that we are interested in.  For brevity we just
// show and encode a part of the complete document.
//..
// {
//   "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
//     }
//   ]
// }
//..
// First, we specify the result that we are expecting to get:
//..
//  const bsl::string EXPECTED =
//      "{\n"
//      "  \"Stocks\" : [\n"
//      "    {\n"
//      "      \"Name\" : \"International Business Machines Corp\",\n"
//      "      \"Ticker\" : \"IBM US Equity\",\n"
//      "      \"Last Price\" : 149.3,\n"
//      "      \"Dividend Yield\" : 3.95\n"
//      "    },\n"
//      "    {\n"
//      "      \"Name\" : \"Apple Inc\",\n"
//      "      \"Ticker\" : \"AAPL US Equity\",\n"
//      "      \"Last Price\" : 205.8,\n"
//      "      \"Dividend Yield\" : 1.4\n"
//      "    }\n"
//      "  ]\n"
//      "}";
//..
// Then, to encode this JSON document we create a 'baljsn::Formatter' object.
// Since we want the document to be written in a pretty, easy to understand
// format we will specify the 'true' for the 'usePrettyStyle' option and
// provide an appropriate initial indent level and spaces per level values:
//..
//  bsl::ostringstream os;
//  baljsn::Formatter formatter(os, true, 0, 2);
//..
// Next, we start calling the sequence of methods requires to produce this
// document.  We start with the top level object and add an element named
// 'Stocks' to it:
//..
//  formatter.openObject();
//  formatter.openMember("Stocks");
//..
// Then, we see that 'Stocks' is an array element so we specify the start of
// the array:
//..
//  formatter.openArray();
//..
// Next, each element within 'Stocks' is an object that contains the
// information for an individual stock.  So we have to output an object here:
//..
//  formatter.openObject();
//..
// We now encode the other elements in the stock object.  The 'closeMember'
// terminates the element by adding a ',' at the end.  For the last element in
// an object do not call the 'closeMember' method.
//..
//  formatter.openMember("Name");
//  formatter.putValue("International Business Machines Corp");
//  formatter.closeMember();
//
//  formatter.openMember("Ticker");
//  formatter.putValue("IBM US Equity");
//  formatter.closeMember();
//
//  formatter.openMember("Last Price");
//  formatter.putValue(149.3);
//  formatter.closeMember();
//
//  formatter.openMember("Dividend Yield");
//  formatter.putValue(3.95);
//  // Note no call to 'closeMember' for the last element
//..
// Then, close the first stock object and separate it from the second one using
// the 'addArrayElementSeparator' method.
//..
//  formatter.closeObject();
//  formatter.addArrayElementSeparator();
//..
// Next, we add another stock object.  But we don't need to separate it as it
// is the last one.
//..
//  formatter.openObject();
//
//  formatter.openMember("Name");
//  formatter.putValue("Apple Inc");
//  formatter.closeMember();
//
//  formatter.openMember("Ticker");
//  formatter.putValue("AAPL US Equity");
//  formatter.closeMember();
//
//  formatter.openMember("Last Price");
//  formatter.putValue(205.8);
//  formatter.closeMember();
//
//  formatter.openMember("Dividend Yield");
//  formatter.putValue(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());
//..

#include <balscm_version.h>

#include <baljsn_printutil.h>

#include <bdlb_print.h>

#include <bdlc_bitarray.h>

#include <bsl_ostream.h>

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

#include <bsl_string.h>
#include <bsl_string_view.h>

namespace BloombergLP {
namespace baljsn {

class EncoderOptions;

                          // ===============
                          // class Formatter
                          // ===============

class Formatter {
    // 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).

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

    bool              d_usePrettyStyle;      // encoding style

    int               d_indentLevel;         // current indentation level

    int               d_spacesPerLevel;      // spaces per indentation level

    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'.

    // 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 'd_usePrettyStyle' is 'true' before indenting.

    // PRIVATE ACCESSORS
    bool isArrayElement() const;
        // Return 'true' if the value being encoded is an element of an array,
        // and 'false' otherwise.  A value is identified as an element of an
        // array if 'openArray' was called on this object and was not
        // subsequently followed by either an 'openObject' or 'closeArray'
        // call.

  public:
    // CREATORS
    Formatter(bsl::ostream&     stream,
              bool              usePrettyStyle     = false,
              int               initialIndentLevel = 0,
              int               spacesPerLevel     = 0,
              bslma::Allocator *basicAllocator     = 0);
        // Create a 'Formatter' object using the specified 'stream'.
        // Optionally specify 'usePrettyStyle' to inform the formatter whether
        // the pretty encoding style should be used when writing data.  If
        // 'usePrettyStyle' is not specified then the data is written in a
        // compact style.  If 'usePrettyStyle' is specified, additionally
        // specify 'initialIndentLevel' and 'spacesPerLevel' to provide the
        // initial indentation level and spaces per level at which the data
        // should be formatted.  If 'initialIndentLevel' or 'spacesPerLevel' is
        // not specified then an initial value of '0' is used for both
        // parameters.  If 'usePrettyStyle' is 'false' then
        // 'initialIndentLevel' and 'spacesPerLevel' are both ignored.
        // Optionally specify a 'basicAllocator' used to supply memory.  If
        // 'basicAllocator' is 0, the currently installed default allocator is
        // used.

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

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

    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 this
        // 'Formatter' is currently formatting an object.

    void openArray(bool formatAsEmptyArray = false);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of an array (referred to as an
        // "array" in JSON).  Optionally specify 'formatAsEmptyArray' denoting
        // if the array being opened should be formatted as an empty array.  If
        // 'formatAsEmptyArray' is not specified then the array being opened is
        // formatted as an array having elements.  Note that the formatting
        // (and as a consequence the 'formatAsEmptyArray') is relevant only if
        // this formatter encodes in the pretty style and is ignored otherwise.

    void closeArray(bool formatAsEmptyArray = false);
        // 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 'formatAsEmptyArray' denoting
        // if the array being closed should be formatted as an empty array.  If
        // 'formatAsEmptyArray' is not specified then the array being closed is
        // formatted as an array having elements.  The behavior is undefined
        // unless this 'Formatter' is currently formatting an array.  Note that
        // the formatting (and as a consequence the 'formatAsEmptyArray') is
        // relevant only if this formatter encodes in the pretty style and is
        // ignored otherwise.

    int openMember(const bsl::string_view& name);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of a member (referred to as a
        // "name/value pair" in JSON) having the specified 'name'.  Return 0 on
        // success and a non-zero value otherwise.

    void putNullValue();
        // Print onto the stream supplied at construction the value
        // corresponding to a null element.

    template <class TYPE>
    int putValue(const TYPE& value, const EncoderOptions *options = 0);
        // Print onto the stream supplied at construction the specified
        // 'value'.  Optionally specify 'options' according which 'value'
        // should be encoded.  Return 0 on success and a non-zero value
        // otherwise.

    void closeMember();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the end of an member (referred to as a
        // "name/value pair" in JSON).  The behavior is undefined unless this
        // 'Formatter' is currently formatting a member.

    void addArrayElementSeparator();
        // Print onto the stream supplied at construction the sequence of
        // characters designating an array element separator (i.e., ',').  The
        // behavior is undefined unless this 'Formatter' is currently
        // formatting a member.

    // ACCESSORS
    int nestingDepth() const;
        // Return the number of currently open nested objects or arrays.
};

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

                        // ---------------
                        // class Formatter
                        // ---------------

// PRIVATE MANIPULATORS
inline
void Formatter::indent()
{
    bdlb::Print::indent(d_outputStream, d_indentLevel, d_spacesPerLevel);
}

// PRIVATE ACCESSORS
inline
bool Formatter::isArrayElement() const
{
    BSLS_ASSERT(d_callSequence.length() >= 1);

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

// MANIPULATORS
inline
void Formatter::putNullValue()
{
    if (d_usePrettyStyle && isArrayElement()) {
        indent();
    }
    d_outputStream << "null";
}

template <class TYPE>
int Formatter::putValue(const TYPE& value, const EncoderOptions *options)
{
    if (d_usePrettyStyle && isArrayElement()) {
        indent();
    }
    return baljsn::PrintUtil::printValue(d_outputStream, value, options);
}

// ACCESSORS
inline
int Formatter::nestingDepth() const
{
    // The call sequence contains a "dummy" initial element, so subtract one
    // from the length.
    return static_cast<int>(d_callSequence.length()) - 1;
}

}  // close package namespace

}  // close enterprise namespace

#endif

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