123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
/*******************************************************************************

        copyright:      Copyright (c) 2007 Kris Bell. All rights reserved

        license:        BSD style: $(LICENSE)

        version:        Initial release: Oct 2007

        author:         Kris

        These classes represent a simple means of reading and writing
        discrete data types as binary values, with an option to invert
        the endian order of numeric values.

        Arrays are treated as untyped byte streams, with an optional
        length-prefix, and should otherwise be explicitly managed at
        the application level. We'll add additional support for arrays
        and aggregates in future.

*******************************************************************************/

module tango.io.stream.Data;

private import tango.core.ByteSwap;

private import tango.io.device.Conduit;

private import tango.io.stream.Buffered;

version = DataIntArrayLength;

/*******************************************************************************

        A simple way to read binary data from an arbitrary InputStream,
        such as a file:
        ---
        auto input = new DataInput (new File ("path"));
        auto x = input.int32;
        auto y = input.float64;
        auto l = input.read (buffer);           // read raw data directly
        auto s = cast(char[]) input.array;      // read length, allocate space
        input.close;
        ---

*******************************************************************************/

class DataInput : InputFilter
{
        public alias array     get;             /// Old name aliases.
        public alias boolean   getBool;         /// ditto
        public alias int8      getByte;         /// ditto
        public alias int16     getShort;        /// ditto
        public alias int32     getInt;          /// ditto
        public alias int64     getLong;         /// ditto
        public alias float32   getFloat;        /// ditto
        public alias float64   getDouble;       /// ditto

        /// Endian variations.
        public enum
        {
                Native  = 0, ///
                Network = 1, ///
                Big     = 1, ///
                Little  = 2  ///
        }

        private bool            flip;
        protected InputStream   input;
        private Allocate        allocator;

        private alias void[] delegate (size_t) Allocate;

        /***********************************************************************

                Propagate ctor to superclass.

        ***********************************************************************/

        this (InputStream stream)
        {
                super (input = BufferedInput.create (stream));

                allocator = (size_t bytes){return new void[bytes];};
        }

        /***********************************************************************

                Set the array allocator.

        ***********************************************************************/

        final DataInput allocate (Allocate allocate)
        {
                allocator = allocate;
                return this;
        }

        /***********************************************************************

                Set current endian translation.

        ***********************************************************************/

        final DataInput endian (int e)
        {
                version (BigEndian)
                         flip = e is Little;
                   else
                      flip = e is Network;
                return this;
        }

        /***********************************************************************

                Read an array back into a user-provided workspace. The
                space must be sufficiently large enough to house all of
                the array, and the actual number of bytes is returned.

                Note that the size of the array is written as an integer
                prefixing the array content itself.  Use read(void[]) to
                eschew this prefix.

        ***********************************************************************/

        final size_t array (void[] dst)
        {
                version(DataIntArrayLength)
                    auto len = cast(size_t)int32();
                else
                    auto len = cast(size_t)int64();
                if (len > dst.length)
                    conduit.error ("DataInput.readArray :: dst array is too small");
                eat (dst.ptr, len);
                return len;
        }

        /***********************************************************************

                Read an array back from the source, with the assumption
                it has been written using DataOutput.put() or otherwise
                prefixed with an integer representing the total number
                of bytes within the array content. That's *bytes*, not
                elements.

                An array of the appropriate size is allocated either via
                the provided delegate, or from the heap, populated and
                returned to the caller. Casting the return value to an
                appropriate type will adjust the number of elements as
                required:
                ---
                auto text = cast(char[]) input.get;
                ---

        ***********************************************************************/

        final void[] array ()
        {
                version(DataIntArrayLength)
                    auto len = cast(size_t)int32();
                else
                    auto len = cast(size_t)int64();
                auto dst = allocator (len);
                eat (dst.ptr, len);
                return dst;
        }

        /***********************************************************************

        ***********************************************************************/

        final bool boolean ()
        {
                bool x;
                eat (&x, x.sizeof);
                return x;
        }

        /***********************************************************************

        ***********************************************************************/

        final byte int8 ()
        {
                byte x;
                eat (&x, x.sizeof);
                return x;
        }

        /***********************************************************************

        ***********************************************************************/

        final short int16 ()
        {
                short x;
                eat (&x, x.sizeof);
                if (flip)
                    ByteSwap.swap16(&x, x.sizeof);
                return x;
        }

        /***********************************************************************

        ***********************************************************************/

        final int int32 ()
        {
                int x;
                eat (&x, x.sizeof);
                if (flip)
                    ByteSwap.swap32(&x, x.sizeof);
                return x;
        }

        /***********************************************************************

        ***********************************************************************/

        final long int64 ()
        {
                long x;
                eat (&x, x.sizeof);
                if (flip)
                    ByteSwap.swap64(&x, x.sizeof);
                return x;
        }

        /***********************************************************************

        ***********************************************************************/

        final float float32 ()
        {
                float x;
                eat (&x, x.sizeof);
                if (flip)
                    ByteSwap.swap32(&x, x.sizeof);
                return x;
        }

        /***********************************************************************

        ***********************************************************************/

        final double float64 ()
        {
                double x;
                eat (&x, x.sizeof);
                if (flip)
                    ByteSwap.swap64(&x, x.sizeof);
                return x;
        }

        /***********************************************************************

        ***********************************************************************/

        final override size_t read (void[] data)
        {
                eat (data.ptr, data.length);
                return data.length;
        }

        /***********************************************************************

        ***********************************************************************/

        private final void eat (void* dst, size_t bytes)
        {
                while (bytes)
                      {
                      auto i = input.read (dst [0 .. bytes]);
                      if (i is Eof)
                          input.conduit.error ("DataInput :: unexpected eof while reading");
                      bytes -= i;
                      dst += i;
                      }
         }
}


/*******************************************************************************

        A simple way to write binary data to an arbitrary OutputStream,
        such as a file:
        ---
        auto output = new DataOutput (new File ("path", File.WriteCreate));
        output.int32   (1024);
        output.float64 (3.14159);
        output.array   ("string with length prefix");
        output.write   ("raw array, no prefix");
        output.close;
        ---

*******************************************************************************/

class DataOutput : OutputFilter
{
        public alias array      put;            /// Old name aliases.
        public alias boolean    putBool;        /// ditto
        public alias int8       putByte;        /// ditto
        public alias int16      putShort;       /// ditto
        public alias int32      putInt;         /// ditto
        public alias int64      putLong;        /// ditto
        public alias float32    putFloat;       /// ditto
        public alias float64    putFloat;       /// ditto

        /// Endian variations.
        public enum
        {
                Native  = 0, ///
                Network = 1, ///
                Big     = 1, ///
                Little  = 2  ///
        }

        private bool            flip;
        private OutputStream    output;

        /***********************************************************************

                Propagate ctor to superclass.

        ***********************************************************************/

        this (OutputStream stream)
        {
                super (output = BufferedOutput.create (stream));
        }

        /***********************************************************************

                Set current endian translation.

        ***********************************************************************/

        final DataOutput endian (int e)
        {
                version (BigEndian)
                         flip = e is Little;
                   else
                      flip = e is Network;
                return this;
        }

        /***********************************************************************

                Write an array to the target stream. Note that the size
                of the array is written as an integer prefixing the array
                content itself. Use write(void[]) to eschew this prefix.

        ***********************************************************************/

        final ulong array (const(void)[] src)
        {
                auto len = src.length;
                version(DataIntArrayLength)
                    int32 (cast(int)len);
                else
                    int64 (len);
                output.write (src);
                return len;
        }

        /***********************************************************************

        ***********************************************************************/

        final void boolean (bool x)
        {
                eat (&x, x.sizeof);
        }

        /***********************************************************************

        ***********************************************************************/

        final void int8 (byte x)
        {
                eat (&x, x.sizeof);
        }

        /***********************************************************************

        ***********************************************************************/

        final void int16 (short x)
        {
                if (flip)
                    ByteSwap.swap16 (&x, x.sizeof);
                eat (&x, x.sizeof);
        }

        /***********************************************************************

        ***********************************************************************/

        final void int32 (int x)
        {
                if (flip)
                    ByteSwap.swap32 (&x, x.sizeof);
                eat (&x, uint.sizeof);
        }

        /***********************************************************************

        ***********************************************************************/

        final void int64 (long x)
        {
                if (flip)
                    ByteSwap.swap64 (&x, x.sizeof);
                eat (&x, x.sizeof);
        }

        /***********************************************************************

        ***********************************************************************/

        final void float32 (float x)
        {
                if (flip)
                    ByteSwap.swap32 (&x, x.sizeof);
                eat (&x, x.sizeof);
        }

        /***********************************************************************

        ***********************************************************************/

        final void float64 (double x)
        {
                if (flip)
                    ByteSwap.swap64 (&x, x.sizeof);
                eat (&x, x.sizeof);
        }

        /***********************************************************************

        ***********************************************************************/

        final override size_t write (const(void)[] data)
        {
                eat (data.ptr, data.length);
                return data.length;
        }

        /***********************************************************************

        ***********************************************************************/

        private final void eat (in void* src, size_t bytes)
        {
                auto count = output.write (src[0..bytes]);
                assert (count is bytes);
        }
}


/*******************************************************************************

*******************************************************************************/

debug (UnitTest)
{
        import tango.io.device.Array;

        unittest
        {
                auto buf = new Array(32);

                auto output = new DataOutput (buf);
                output.array ("blah blah".dup);
                output.int32 (1024);

                auto input = new DataInput (buf);
                assert (input.array(new char[9]) is 9);
                assert (input.int32() is 1024);
        }
}


/*******************************************************************************

*******************************************************************************/

debug (Data)
{
        import tango.io.device.Array;

        void main()
        {
                auto buf = new Array(64);

                auto output = new DataOutput (buf);
                output.array ("blah blah");
                output.int32 (1024);

                auto input = new DataInput (buf);
                assert (input.array.length is 9);
                assert (input.int32 is 1024);
        }
}