FieldPath.h

// FieldPath.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2006

@import "cxObject.h"

#include "Ceda/cxUtils/StreamInterfaces.h"
#include "Ceda/cxUtils/VariableLengthSerialise.h"

// yuk- needed for FieldPathArchive
#include "Ceda/cxUtils/BasicTypes.h"
#include "Ceda/cxUtils/Archive.h"
#include "Ceda/cxUtils/PagedBuffer.h"
#include "Ceda/cxUtils/PagedBufferAsStream.h"

namespace ceda
{
class Archive;

/*
TODO:

*   The FieldPath class as written is suitable when deserialising a path.  However when *generating*
    a path it should be possible to provide a special implementation for the given size.  In the
    common case where the path fits into a local buffer we can avoid the need for a destructor to run.
    This should increase performance of operation generation.
    We of course need to ensure binary compatabilty!
*/

@def int PATH_LOCALBUF_SIZE = 4*4

// Binary compatible with FieldPath!  It is safe to create a FieldPath4 on the frame, and reinterpret cast it
// to a const FieldPath&
class FieldPath4
{
public:
    FieldPath4(ssize_t v) : m_size(4), m_capacity(4), m_localBuffer(v) {}

private:
    ssize_t AvoidClangWarning() { return m_size+m_capacity+m_localBuffer; }

private:    
    ssize_t m_size;
    ssize_t m_capacity;
    ssize_t m_localBuffer;
};

class FieldPathArchive;

class @api FieldPath
{
public:
    FieldPath() :
        m_size(0),
        m_capacity(PATH_LOCALBUF_SIZE)
    {
    }

    FieldPath(const void* buffer, ssize_t size);

    @if (false)
    {
        // Constructors that take ints on the command line
        @scope
        {
            @def mWriteBuffer(buffer,int i) =
            {
                @if (i > 0)
                {
                    mWriteBuffer(buffer,i-1)
                    *reinterpret_cast<int32*>(&buffer[@((i-1)*4)]) = v@@i;
                }
            }
            @def mArgs(int i) =
            {
                @if (i == 1) {int32 v1} @else {mArgs(i-1),int32 v@@i}
            }
            @let ssize_t i = 1
            @while(i*4 <= PATH_LOCALBUF_SIZE)
            {
                @if (i == 1) {explicit}
                FieldPath(mArgs(i)) : m_size(@(i*4)), m_capacity(PATH_LOCALBUF_SIZE)
                {
                    mWriteBuffer(m_localBuffer,i)
                }
                @let i = i+1
            }
            @while(i*4 <= PATH_HEAPBUF_SIZE)
            {
                FieldPath(mArgs(i)) : m_size(@(i*4)), m_capacity(@(i*4))
                {
                    m_heapBuffer = new octet_t[m_capacity];
                    mWriteBuffer(m_heapBuffer,i)
                }
                @let i = i+1
            }
        }
    }
    
    ~FieldPath();

    FieldPath(const FieldPath& rhs);
    FieldPath& operator=(const FieldPath& rhs);

    /////////////// InitBuffer ///////////////
    /*
    Used for the efficient initialisation of paths which can be stored in
    the local buffer (i.e. size <= PATH_LOCALBUF_SIZE) and for which the size is known
    up front.
    
    Must be called just after default construction of the path.
    Returns the address of the buffer to subsequently be initialised by the client
    
    The following example initialises a path to [0x01 0x01 0x02 0x01 0x04]:
    
        FieldPath path;
        octet_t* p = path.InitLocalBuffer(5);
        * (int32*) p = 0x01020101;      // initialise first 4 bytes
        p[4] = 0x04;                    // initialise fifth byte
        
    Unfortunately it is not possible to avoid having the client know about the distinction
    between local and heap allocation buffers, because MSVC has been found inadequate
    at compiler optimisation.  It seems that once it sees a function perform heap 
    allocation, it refuses to optimise it (even though it could in theory find that heap 
    allocation is impossible in that instance).
    */
    octet_t* InitLocalBuffer(ssize_t size)
    {
        cxAssert(m_capacity == PATH_LOCALBUF_SIZE);
        cxAssert(0 <= size && size <= PATH_LOCALBUF_SIZE);
        m_size = size;
        return m_localBuffer;
    }

    // As for InitLocalBuffer, except when it is known that size > PATH_LOCALBUF_SIZE
    // Must only be called after default construction of the path.
    octet_t* InitHeapBuffer(ssize_t size);
    
    // Like InitLocalBuffer() and InitHeapBuffer() without the constraint on the size,
    // or the assumption that it is called just after the default ctor.
    octet_t* InitBuffer(ssize_t size);

    /////////////// BeginReserve - EndReserve ///////////////
    
    /*
    Used for the efficient initialisation of paths for which there is an upper bound on
    the size, such that it is known it will fit into the local buffer 
    (i.e. size <= PATH_LOCALBUF_SIZE), but the actual size is not known until after 
    writing the content.
    
    Must be called just after default construction of the path.
    
    Returns the address of the buffer to be initialised by the calling function
    
    Example:
        FieldPath path;
        octet_t* p = path.BeginReserveLocal();
        * (int32*) p = 0x01020101; P += 4;           // initialise first 4 bytes
        p = SerialiseVariableLengthInteger(p,x);      // Write variable length x (1-5 bytes)
        *p++ = 0x04;
        path.EndReserveLocal(p);
    */

    octet_t* BeginReserveLocal()
    {
        cxAssert(m_capacity == PATH_LOCALBUF_SIZE);
        return m_localBuffer;
    }
    void EndReserveLocal(octet_t* pos)
    {
        cxAssert(m_capacity == PATH_LOCALBUF_SIZE);
        m_size = pos - m_localBuffer;
        cxAssert(0 <= m_size && m_size <= m_capacity);
    }
    
    octet_t* BeginReserveHeap(ssize_t capacity);
    void EndReserveHeap(octet_t* pos)
    {
        cxAssert(m_size == 0);
        cxAssert(m_capacity > PATH_LOCALBUF_SIZE);
        m_size = pos - m_heapBuffer;
        cxAssert(0 <= m_size && m_size <= m_capacity);
    }

    octet_t* BeginReserve(ssize_t capacity);
    void EndReserve(octet_t* pos);
    
    
    ///////////////
    void reserve(ssize_t capacity);
    
    void clear();
    bool empty() const { return m_size == 0; }
    
    /// Append the given buffer to the end of the path
    void Append(const void* buffer, ssize_t numBytes);
    
    // Append x by writing as a variable length uint32.  Returns the number of bytes
    // added to the path
    ssize_t AppendUint32(uint32 x)
    {
        octet_t buffer[5];
        ssize_t n = SerialiseVariableLengthInteger(buffer,x) - buffer;
        Append(buffer,n);
        return n;    
    }
    ssize_t Append_ssize_t(ssize_t x)
    {
        octet_t buffer[5];
        ssize_t n = SerialiseVariableLengthInteger(buffer,x) - buffer;
        Append(buffer,n);
        return n;    
    }
    
    // The inverse of Append
    void EraseTail(ssize_t numBytes)
    {
        cxAssert(m_size >= numBytes);
        m_size -= numBytes;
    }

    bool operator==(const FieldPath& rhs) const;
    bool operator!=(const FieldPath& rhs) const { return !operator==(rhs); }
    bool operator<(const FieldPath& rhs) const;

    const octet_t* begin() const { return (m_capacity > PATH_LOCALBUF_SIZE) ? m_heapBuffer : m_localBuffer; }
    const octet_t* end() const { return begin() + m_size; }

    octet_t* begin() { return (m_capacity > PATH_LOCALBUF_SIZE) ? m_heapBuffer : m_localBuffer; }
    octet_t* end() { return begin() + m_size; }

    ssize_t size() const { return m_size; }

    void Write(xostream& os) const;

    void Serialise(Archive& ar) const;
    void Deserialise(InputArchive& ar);

private:
    // Current size in bytes.  Must have 0 <= m_size <= m_capacity
    ssize_t m_size;
    
    // If m_capacity > PATH_LOCALBUF_SIZE then the heap allocated buffer is used,
    // otherwise the local buffer
    ssize_t m_capacity;

    union
    {
        octet_t* m_heapBuffer;
        octet_t m_localBuffer[PATH_LOCALBUF_SIZE];
    };
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// Adapter FieldPath to IOutputStream

struct PathAsOutputStream : public IOutputStream
{
    PathAsOutputStream(FieldPath& path) : m_path(path) {}    
    
    // Implementation of IOutputStream
    virtual void WriteStream(const void* buffer, ssize_t numBytes)
    {
        m_path.Append(buffer,numBytes);
    }
    virtual void FlushStream() {}

    FieldPath& m_path;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// FieldPathArchive

class @api FieldPathArchive : public Archive
{
public:
    FieldPathArchive() :
        Archive(&m_os),
        m_os(m_pb)
    {
    }
    
    void InitPath(FieldPath& path);

private:
    PagedBufferToOutputStreamAdapter m_os;
    PagedBuffer m_pb;
    
    friend class FieldPath;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
class AppendPath
{
public:
    AppendPath(FieldPath& path, ssize_t index) :
        path_(path)
    {
        n_ = path_.Append_ssize_t(index);
    }
    ~AppendPath()
    {
        path_.EraseTail(n_);
    }
private:
    FieldPath& path_;
    ssize_t n_;
};

} // namespace ceda