Basically what I'm thinking is that IO is broken up into three key classes:
InStream: mixin for reading from input stream with convenience for everything, binary primitives, strings, etc;
OutStream: mixin for writing to output stream with convenience for everything
Buf: random access buffer with pos(), seek(), get/set/[] support. It mixes in both InStream and OutStream to use all the convenience methods (which we can actually do with a mixin). In both cases you read and write to current position.
Actually creating IO streams will be target based rather than a bunch of crap like FileInStream, FileOutStream, etc - that will keep the classes in the public API to a bare minimum. For example File would have these methods:
Here is what I've been stubbing out for the specification (OutStream will mimic InStream). Take a look and see what you think.
**
** Buf is used to model a buffer of bytes with random access. Buf is
** typically backed by a file or a block of memory. Buf implements the
** InStream and OutStream mixins to read and write into the buffer using
** a configurable position.
**
class Buf
mixin InStream, OutStream
{
//////////////////////////////////////////////////////////////////////////
// Contents
//////////////////////////////////////////////////////////////////////////
**
** Get the byte at the specified index. A negative index may be
** used to access from the end of the buffer. For example get(-1)
** is translated into get(size()-1). The get method is accessed
** via the [] shortcut operator. Throw IndexErr is index out of
** range.
**
virtual Int get(Int index)
**
** Set is used to overwrite the byte at the specified the index. A
** negative index may be used to access an index from the end of the
** buffer. The set method is accessed via the []= shortcut operator.
** Return this. Throw IndexErr if index is out of range.
**
virtual Buf set(Int index, Int byte)
//////////////////////////////////////////////////////////////////////////
// Position
//////////////////////////////////////////////////////////////////////////
**
** Return the current number of bytes available in the buffer.
**
virtual Int size()
**
** Return the current position for the next read or write. The
** position is always between 0 and size().
**
virtual Int pos()
**
** Set the current position to the specified byte offset. A
** negative index may be used to access from the end of the buffer.
** For example seek(-1) is translated into seek(size()-1).
** Return this.
**
virtual Buf seek(Int pos)
}
**
** InStream is used to read stream based input. An InStream is always
** buffered for performance. Often an InStream is configured with a
** character encoding used to map bytes into Unicode characters (the
** default is UTF-8).
**
** @author Brian Frank
** @creation 16 Mar 06
**
mixin InStream
{
//////////////////////////////////////////////////////////////////////////
// Abstract
//////////////////////////////////////////////////////////////////////////
**
** Read the next unsigned byte from the input stream.
** Return -1 if at end of stream.
**
abstract Int read()
**
** Attempt to read the next n bytes into the Buf at it's current
** position. Return the number of bytes actually read into the
** buffer or -1 if at end of stream.
**
abstract Int readBuf(Buf buf, Int n)
//////////////////////////////////////////////////////////////////////////
// Primitive Reads
//////////////////////////////////////////////////////////////////////////
**
** Read the next byte as a signed 8-bit number.
**
virtual Int readS1()
**
** Read the next two bytes as an unsigned 16-bit number
** in network byte order.
**
virtual Int readU2()
**
** Read the next two bytes as a signed 16-bit number
** in network byte order.
**
virtual Int readS2()
**
** Read the next four bytes as an unsigned 32-bit number
** in network byte order.
**
virtual Int readU4()
**
** Read the next four bytes as a signed 32-bit number
** in network byte order.
**
virtual Int readS4()
**
** Read the next eight bytes as a signed 64-bit number
** in network byte order.
**
virtual Int readS8()
**
** Read the next four bytes as a 32-bit floating point number.
**
virtual Real readF4()
**
** Read the next eight bytes as a 64-bit floating point number.
**
virtual Real readF8()
**
** Read the next byte and return true if nonzero.
**
virtual Bool readBool()
//////////////////////////////////////////////////////////////////////////
// Buf Reads
//////////////////////////////////////////////////////////////////////////
**
** Read the entire contents of the stream into a memory Buf.
**
virtual Buf readAllBuf()
**
** Synchronously read the next n bytes from the stream into the Buf
** at it's current position. Block until exactly n bytes have been
** read or throw IOErr if end of stream is reached first.
**
virtual Void readBufSync(Buf buf, Int n)
//////////////////////////////////////////////////////////////////////////
// Str Reads
//////////////////////////////////////////////////////////////////////////
**
** Get the current character encoding being used to translate
** bytes into characters.
**
virtual Str charEncoding()
**
** Read the next line from the input stream as a Str based on the
** configured character encoding. A line is terminated by \n,
** \r\n, \r, or EOF. The Str returned never contains the
** trailing newline. Return null if the end of stream has been
** reached.
**
virtual Str readLine()
**
** Read the entire stream into a list of Str lines based on the
** configured character encoding. Each Str in the list maps
** to a line terminated by \n, \r\n, \r, or EOF. The Str lines
** themselves do not contain a trailing newline. Empty
** lines are returned as the empty Str "".
**
virtual Str[] readAllLines()
**
** Read the entire stream into a Str based on the configured
** character encoding. If the normalizeNewlines flag is true,
** then all occurances of \r\n or \r newlines are normalized
** into \n.
**
virtual Str readAllStr(Bool normalizeNewlines := true)
**
** Read a Str in modified UTF-8 format according
** the java.io.DataInput specification.
**
virtual Str readUtf()
//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////
**
** Attempt to skip n number of bytes. Return the number of bytes
** actually skipped which may be equal to or lesser than n.
**
virtual Int skip(Int n)
**
** Pipe bytes from this input stream to the specified output stream.
** If n is specified, then block until exactly n bytes have been
** read or throw IOErr if end of stream is reached first. If n is
** -1 then the entire contents of this input stream are piped. Return
** the number of bytes piped to the output stream.
**
virtual Int pipe(OutStream out, Int n := -1)
**
** Return true if this stream has been closed.
**
virtual Bool closed()
**
** Close the input stream. This method is guaranteed to never
** throw an IOErr.
**
virtual Void close()
}
andyFri 17 Mar 2006
I think that looks good. The important thing is to encapsulate all the frickin over-engineering in Java into a single usable class. The primitive read methods are going to be sweet when working with binary stuff.
brian Fri 17 Mar 2006
Basically what I'm thinking is that IO is broken up into three key classes:
Actually creating IO streams will be target based rather than a bunch of crap like FileInStream, FileOutStream, etc - that will keep the classes in the public API to a bare minimum. For example File would have these methods:
Here is what I've been stubbing out for the specification (OutStream will mimic InStream). Take a look and see what you think.
andy Fri 17 Mar 2006
I think that looks good. The important thing is to encapsulate all the frickin over-engineering in Java into a single usable class. The primitive read methods are going to be sweet when working with binary stuff.