#53 IO Design

brian Fri 17 Mar 2006

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:

class File
{
  Buf open() // random access
  InStream openIn()  // returns internal FileInStream
  OutStream openOut() // returns internal FileOutStream
}

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()

}

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.

Login or Signup to reply.