root/src/kits/shared/JsonMessageWriter.cpp
/*
 * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>
 * Distributed under the terms of the MIT License.
 */


#include "JsonMessageWriter.h"


namespace BPrivate {

/*! The class and sub-classes of it are used as a stack internal to the
    BJsonMessageWriter class.  As the JSON is parsed, the stack of these
    internal listeners follows the stack of the JSON parsing in terms of
    containers; arrays and objects.
*/

class BStackedMessageEventListener : public BJsonEventListener {
public:
                                                                BStackedMessageEventListener(
                                                                        BJsonMessageWriter* writer,
                                                                        BStackedMessageEventListener* parent,
                                                                        uint32 messageWhat);
                                                                BStackedMessageEventListener(
                                                                        BJsonMessageWriter* writer,
                                                                        BStackedMessageEventListener* parent,
                                                                        BMessage* message);
                                                                ~BStackedMessageEventListener();

                                bool                    Handle(const BJsonEvent& event);
                                void                    HandleError(status_t status, int32 line,
                                                                        const char* message);
                                void                    Complete();

                                void                    AddMessage(BMessage* value);

                                status_t                ErrorStatus();
                virtual const char*             NextItemName() = 0;

                                BStackedMessageEventListener*
                                                                Parent();

protected:
                                void                    AddBool(bool value);
                                void                    AddNull();
                                void                    AddDouble(double value);
                                void                    AddString(const char* value);

                virtual bool                    WillAdd();
                virtual void                    DidAdd();

                                void                    SetStackedListenerOnWriter(
                                                                        BStackedMessageEventListener*
                                                                        stackedListener);

                        BJsonMessageWriter*     fWriter;
                        bool                            fOwnsMessage;
                        BStackedMessageEventListener
                                                                *fParent;
                        BMessage*                       fMessage;
};


class BStackedArrayMessageEventListener : public BStackedMessageEventListener {
public:
                                                                BStackedArrayMessageEventListener(
                                                                        BJsonMessageWriter* writer,
                                                                        BStackedMessageEventListener* parent);
                                                                BStackedArrayMessageEventListener(
                                                                        BJsonMessageWriter* writer,
                                                                        BStackedMessageEventListener* parent,
                                                                        BMessage* message);
                                                                ~BStackedArrayMessageEventListener();

                                bool                    Handle(const BJsonEvent& event);

                                const char*             NextItemName();

protected:
                                void                    DidAdd();

private:
                                uint32                  fCount;
                                BString                 fNextItemName;

};


class BStackedObjectMessageEventListener : public BStackedMessageEventListener {
public:
                                                                BStackedObjectMessageEventListener(
                                                                        BJsonMessageWriter* writer,
                                                                        BStackedMessageEventListener* parent);
                                                                BStackedObjectMessageEventListener(
                                                                        BJsonMessageWriter* writer,
                                                                        BStackedMessageEventListener* parent,
                                                                        BMessage* message);
                                                                ~BStackedObjectMessageEventListener();

                                bool                    Handle(const BJsonEvent& event);

                                const char*             NextItemName();

protected:
                                bool                    WillAdd();
                                void                    DidAdd();
private:
                                BString                 fNextItemName;
};

} // namespace BPrivate

using BPrivate::BStackedMessageEventListener;
using BPrivate::BStackedArrayMessageEventListener;
using BPrivate::BStackedObjectMessageEventListener;


// #pragma mark - BStackedMessageEventListener


BStackedMessageEventListener::BStackedMessageEventListener(
        BJsonMessageWriter* writer,
        BStackedMessageEventListener* parent,
        uint32 messageWhat)
{
        fWriter = writer;
        fParent = parent;
        fOwnsMessage = true;
        fMessage = new BMessage(messageWhat);
}


BStackedMessageEventListener::BStackedMessageEventListener(
        BJsonMessageWriter* writer,
        BStackedMessageEventListener* parent,
        BMessage* message)
{
        fWriter = writer;
        fParent = parent;
        fOwnsMessage = false;
        fMessage = message;
}


BStackedMessageEventListener::~BStackedMessageEventListener()
{
        if (fOwnsMessage)
                delete fMessage;
}


bool
BStackedMessageEventListener::Handle(const BJsonEvent& event)
{
        if (fWriter->ErrorStatus() != B_OK)
                return false;

        switch (event.EventType()) {

                case B_JSON_NUMBER:
                        AddDouble(event.ContentDouble());
                        break;

                case B_JSON_STRING:
                        AddString(event.Content());
                        break;

                case B_JSON_TRUE:
                        AddBool(true);
                        break;

                case B_JSON_FALSE:
                        AddBool(false);
                        break;

                case B_JSON_NULL:
                        AddNull();
                        break;

                case B_JSON_OBJECT_START:
                {
                        SetStackedListenerOnWriter(new BStackedObjectMessageEventListener(
                                fWriter, this));
                        break;
                }

                case B_JSON_ARRAY_START:
                {
                        SetStackedListenerOnWriter(new BStackedArrayMessageEventListener(
                                fWriter, this));
                        break;
                }

                default:
                {
                        HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE,
                                "unexpected type of json item to add to container");
                        return false;
                }
        }

        return ErrorStatus() == B_OK;
}


void
BStackedMessageEventListener::HandleError(status_t status, int32 line,
        const char* message)
{
        fWriter->HandleError(status, line, message);
}


void
BStackedMessageEventListener::Complete()
{
        // illegal state.
        HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE,
                "Complete() called on stacked message listener");
}


void
BStackedMessageEventListener::AddMessage(BMessage* message)
{
        if (WillAdd()) {
                fMessage->AddMessage(NextItemName(), message);
                DidAdd();
        }
}


status_t
BStackedMessageEventListener::ErrorStatus()
{
        return fWriter->ErrorStatus();
}


BStackedMessageEventListener*
BStackedMessageEventListener::Parent()
{
        return fParent;
}


void
BStackedMessageEventListener::AddBool(bool value)
{
        if (WillAdd()) {
                fMessage->AddBool(NextItemName(), value);
                DidAdd();
        }
}

void
BStackedMessageEventListener::AddNull()
{
        if (WillAdd()) {
                fMessage->AddPointer(NextItemName(), (void*)NULL);
                DidAdd();
        }
}

void
BStackedMessageEventListener::AddDouble(double value)
{
        if (WillAdd()) {
                fMessage->AddDouble(NextItemName(), value);
                DidAdd();
        }
}

void
BStackedMessageEventListener::AddString(const char* value)
{
        if (WillAdd()) {
                fMessage->AddString(NextItemName(), value);
                DidAdd();
        }
}


bool
BStackedMessageEventListener::WillAdd()
{
        return true;
}


void
BStackedMessageEventListener::DidAdd()
{
        // noop - present for overriding
}


void
BStackedMessageEventListener::SetStackedListenerOnWriter(
        BStackedMessageEventListener* stackedListener)
{
        fWriter->SetStackedListener(stackedListener);
}


// #pragma mark - BStackedArrayMessageEventListener


BStackedArrayMessageEventListener::BStackedArrayMessageEventListener(
        BJsonMessageWriter* writer,
        BStackedMessageEventListener* parent)
        :
        BStackedMessageEventListener(writer, parent, B_JSON_MESSAGE_WHAT_ARRAY)
{
        fCount = 0;
}


BStackedArrayMessageEventListener::BStackedArrayMessageEventListener(
        BJsonMessageWriter* writer,
        BStackedMessageEventListener* parent,
        BMessage* message)
        :
        BStackedMessageEventListener(writer, parent, message)
{
        message->what = B_JSON_MESSAGE_WHAT_ARRAY;
        fCount = 0;
}


BStackedArrayMessageEventListener::~BStackedArrayMessageEventListener()
{
}


bool
BStackedArrayMessageEventListener::Handle(const BJsonEvent& event)
{
        if (fWriter->ErrorStatus() != B_OK)
                return false;

        switch (event.EventType()) {
                case B_JSON_ARRAY_END:
                {
                        if (fParent != NULL)
                                fParent->AddMessage(fMessage);
                        SetStackedListenerOnWriter(fParent);
                        delete this;
                        break;
                }

                default:
                        return BStackedMessageEventListener::Handle(event);
        }

        return true;
}


const char*
BStackedArrayMessageEventListener::NextItemName()
{
        fNextItemName.SetToFormat("%" B_PRIu32, fCount);
        return fNextItemName.String();
}


void
BStackedArrayMessageEventListener::DidAdd()
{
        BStackedMessageEventListener::DidAdd();
        fCount++;
}


// #pragma mark - BStackedObjectMessageEventListener


BStackedObjectMessageEventListener::BStackedObjectMessageEventListener(
        BJsonMessageWriter* writer,
        BStackedMessageEventListener* parent)
        :
        BStackedMessageEventListener(writer, parent, B_JSON_MESSAGE_WHAT_OBJECT)
{
}


BStackedObjectMessageEventListener::BStackedObjectMessageEventListener(
        BJsonMessageWriter* writer,
        BStackedMessageEventListener* parent,
        BMessage* message)
        :
        BStackedMessageEventListener(writer, parent, message)
{
        message->what = B_JSON_MESSAGE_WHAT_OBJECT;
}


BStackedObjectMessageEventListener::~BStackedObjectMessageEventListener()
{
}


bool
BStackedObjectMessageEventListener::Handle(const BJsonEvent& event)
{
        if (fWriter->ErrorStatus() != B_OK)
                return false;

        switch (event.EventType()) {
                case B_JSON_OBJECT_END:
                {
                        if (fParent != NULL)
                                fParent->AddMessage(fMessage);
                        SetStackedListenerOnWriter(fParent);
                        delete this;
                        break;
                }

                case B_JSON_OBJECT_NAME:
                        fNextItemName.SetTo(event.Content());
                        break;

                default:
                        return BStackedMessageEventListener::Handle(event);
        }

        return true;
}


const char*
BStackedObjectMessageEventListener::NextItemName()
{
        return fNextItemName.String();
}


bool
BStackedObjectMessageEventListener::WillAdd()
{
        if (0 == fNextItemName.Length()) {
                HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE,
                                "missing name for adding value into an object");
                return false;
        }

        return true;
}


void
BStackedObjectMessageEventListener::DidAdd()
{
        BStackedMessageEventListener::DidAdd();
        fNextItemName.SetTo("", 0);
}


// #pragma mark - BJsonMessageWriter


BJsonMessageWriter::BJsonMessageWriter(BMessage& message)
{
        fTopLevelMessage = &message;
        fStackedListener = NULL;
}


BJsonMessageWriter::~BJsonMessageWriter()
{
        BStackedMessageEventListener* listener = fStackedListener;

        while (listener != NULL) {
                BStackedMessageEventListener* nextListener = listener->Parent();
                delete listener;
                listener = nextListener;
        }

        fStackedListener = NULL;
}


bool
BJsonMessageWriter::Handle(const BJsonEvent& event)
{
        if (fErrorStatus != B_OK)
                return false;

        if (fStackedListener != NULL)
                return fStackedListener->Handle(event);
        else {
                switch(event.EventType()) {
                        case B_JSON_OBJECT_START:
                        {
                                SetStackedListener(new BStackedObjectMessageEventListener(
                                        this, NULL, fTopLevelMessage));
                                break;
                        }

                        case B_JSON_ARRAY_START:
                        {
                                fTopLevelMessage->what = B_JSON_MESSAGE_WHAT_ARRAY;
                                SetStackedListener(new BStackedArrayMessageEventListener(
                                        this, NULL, fTopLevelMessage));
                                break;
                        }

                        default:
                        {
                                HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE,
                                        "a message object can only handle an object or an array"
                                        "at the top level");
                                return false;
                        }
                }
        }

        return true; // keep going
}


void
BJsonMessageWriter::Complete()
{
        if (fStackedListener != NULL) {
                HandleError(B_BAD_DATA, JSON_EVENT_LISTENER_ANY_LINE,
                        "unexpected end of input data processing structure");
        }
}


void
BJsonMessageWriter::SetStackedListener(
        BStackedMessageEventListener* listener)
{
        fStackedListener = listener;
}