/*
* Channel.java
*
* Copyright (c) 1997 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and
* redistribution of this file, and for a DISCLAIMER OF ALL
* WARRANTIES.
*
* Included in SQLite3 port to C# for use in testharness only; 2008 Noah B Hart
*
* RCS @(#) $Id: Channel.java,v 1.25 2003/03/08 03:42:43 mdejong Exp $
*/
using System;
using System.Text;
using System.IO;
namespace tcl.lang
{
/// The Channel class provides functionality that will
/// be needed for any type of Tcl channel. It performs
/// generic reads, writes, without specifying how a
/// given channel is actually created. Each new channel
/// type will need to extend the abstract Channel class
/// and override any methods it needs to provide a
/// specific implementation for.
///
public abstract class Channel
{
private void InitBlock()
{
buffering = TclIO.BUFF_FULL;
inputTranslation = TclIO.TRANS_AUTO;
outputTranslation = TclIO.TRANS_PLATFORM;
}
/// This method should be overridden in the subclass to provide
/// a channel specific InputStream object.
///
protected internal abstract Stream InputStream
{
get;
}
/// This method should be overridden in the subclass to provide
/// a channel specific OutputStream object.
///
protected internal abstract Stream OutputStream
{
get;
}
/// Gets the chanName that is the key for the chanTable hashtable.
/// channelId
///
/// Sets the chanName that is the key for the chanTable hashtable.
/// the unique channelId
///
public string ChanName
{
get
{
return chanName;
}
set
{
chanName = value;
}
}
/// Return a string that describes the channel type.
///
/// This is the equivilent of the Tcl_ChannelTypE.typeName field.
///
public abstract string ChanType
{
get;
}
/// Return number of references to this Channel.
public int RefCount
{
get
{
return refCount;
}
}
public bool ReadOnly
{
get
{
return ( ( mode & TclIO.RDONLY ) != 0 );
}
}
public bool WriteOnly
{
get
{
return ( ( mode & TclIO.WRONLY ) != 0 );
}
}
public bool ReadWrite
{
get
{
return ( ( mode & TclIO.RDWR ) != 0 );
}
}
/// Query blocking mode.
/// Set blocking mode.
///
///
/// new blocking mode
///
public bool Blocking
{
get
{
return blocking;
}
set
{
blocking = value;
if ( input != null )
input.Blocking = blocking;
if ( output != null )
output.Blocking = blocking;
}
}
/// Query buffering mode.
/// Set buffering mode
///
///
/// One of TclIO.BUFF_FULL, TclIO.BUFF_LINE,
/// or TclIO.BUFF_NONE
///
public int Buffering
{
get
{
return buffering;
}
set
{
if ( value < TclIO.BUFF_FULL || value > TclIO.BUFF_NONE )
throw new TclRuntimeError( "invalid buffering mode in Channel.setBuffering()" );
buffering = value;
if ( input != null )
input.Buffering = buffering;
if ( output != null )
output.Buffering = buffering;
}
}
/// Query buffer size
/// Tcl_SetChannelBufferSize -> setBufferSize
///
///
/// new buffer size
///
public int BufferSize
{
get
{
return bufferSize;
}
set
{
// If the buffer size is smaller than 10 bytes or larger than 1 Meg
// do not accept the requested size and leave the current buffer size.
if ( ( value < 10 ) || ( value > ( 1024 * 1024 ) ) )
{
return;
}
bufferSize = value;
if ( input != null )
input.BufferSize = bufferSize;
if ( output != null )
output.BufferSize = bufferSize;
}
}
public int NumBufferedInputBytes
{
get
{
if ( input != null )
return input.NumBufferedBytes;
else
return 0;
}
}
public int NumBufferedOutputBytes
{
get
{
if ( output != null )
return output.NumBufferedBytes;
else
return 0;
}
}
/// Returns true if a background flush is waiting to happen.
public bool BgFlushScheduled
{
get
{
// FIXME: Need to query output here
return false;
}
}
/// Query encoding
///
///
/// Name of Channel's Java encoding (null if no encoding)
///
/// Set new Java encoding
internal System.Text.Encoding Encoding
{
get
{
return encoding;
}
set
{
encoding = value;
if ( (System.Object)encoding == null )
bytesPerChar = 1;
else
bytesPerChar = EncodingCmd.getBytesPerChar( encoding );
if ( input != null )
input.Encoding = encoding;
if ( output != null )
output.Encoding = encoding;
// FIXME: Pass bytesPerChar to input and output
}
}
/// Query input translation
/// Set new input translation
public int InputTranslation
{
get
{
return inputTranslation;
}
set
{
inputTranslation = value;
if ( input != null )
input.Translation = inputTranslation;
}
}
/// Query output translation
/// Set new output translation
public int OutputTranslation
{
get
{
return outputTranslation;
}
set
{
outputTranslation = value;
if ( output != null )
output.Translation = outputTranslation;
}
}
/// Query input eof character
/// Set new input eof character
internal char InputEofChar
{
get
{
return inputEofChar;
}
set
{
// Store as a byte, not a unicode character
inputEofChar = (char)( value & 0xFF );
if ( input != null )
input.EofChar = inputEofChar;
}
}
/// Query output eof character
/// Set new output eof character
internal char OutputEofChar
{
get
{
return outputEofChar;
}
set
{
// Store as a byte, not a unicode character
outputEofChar = (char)( value & 0xFF );
if ( output != null )
output.EofChar = outputEofChar;
}
}
/// The read, write, append and create flags are set here. The
/// variables used to set the flags are found in the class TclIO.
///
protected internal int mode;
/// This is a unique name that sub-classes need to set. It is used
/// as the key in the hashtable of registered channels (in interp).
///
private string chanName;
/// How many interpreters hold references to this IO channel?
protected internal int refCount = 0;
/// Tcl input and output objecs. These are like a mix between
/// a Java Stream and a Reader.
///
protected internal TclInputStream input = null;
protected internal TclOutputStream output = null;
/// Set to false when channel is in non-blocking mode.
protected internal bool blocking = true;
/// Buffering (full,line, or none)
protected internal int buffering;
/// Buffer size, in bytes, allocated for channel to store input or output
protected internal int bufferSize = 4096;
/// Name of Java encoding for this Channel.
/// A null value means use no encoding (binary).
///
// FIXME: Check to see if this field is updated after a call
// to "encoding system $enc" for new Channel objects!
protected internal System.Text.Encoding encoding;
protected internal int bytesPerChar;
/// Translation mode for end-of-line character
protected internal int inputTranslation;
protected internal int outputTranslation;
/// If nonzero, use this as a signal of EOF on input.
protected internal char inputEofChar = (char)( 0 );
/// If nonzero, append this to a writeable channel on close.
protected internal char outputEofChar = (char)( 0 );
internal Channel()
{
InitBlock();
Encoding = EncodingCmd.systemJavaEncoding;
}
/// Tcl_ReadChars -> read
///
/// Read data from the Channel into the given TclObject.
///
///
/// is used for TclExceptions.
///
/// the object data will be added to.
///
/// specifies if the read should read the entire
/// buffer (TclIO.READ_ALL), the next line
/// (TclIO.READ_LINE), of a specified number
/// of bytes (TclIO.READ_N_BYTES).
///
/// the number of bytes/chars to read. Used only
/// when the readType is TclIO.READ_N_BYTES.
///
/// the number of bytes read.
/// Returns -1 on EOF or on error.
///
/// TclException is thrown if read occurs on WRONLY channel.
///
/// IOException is thrown when an IO error occurs that was not
/// correctly tested for. Most cases should be caught.
///
internal int read( Interp interp, TclObject tobj, int readType, int numBytes )
{
TclObject dataObj;
checkRead( interp );
initInput();
switch ( readType )
{
case TclIO.READ_ALL:
{
return input.doReadChars( tobj, -1 );
}
case TclIO.READ_LINE:
{
return input.getsObj( tobj );
}
case TclIO.READ_N_BYTES:
{
return input.doReadChars( tobj, numBytes );
}
default:
{
throw new TclRuntimeError( "Channel.read: Invalid read mode." );
}
}
}
/// Tcl_WriteObj -> write
///
/// Write data to the Channel
///
///
/// is used for TclExceptions.
///
/// the TclObject that holds the data to write.
///
public virtual void write( Interp interp, TclObject outData )
{
checkWrite( interp );
initOutput();
// FIXME: Is it possible for a write to happen with a null output?
if ( output != null )
{
output.writeObj( outData );
}
}
/// Tcl_WriteChars -> write
///
/// Write string data to the Channel.
///
///
/// is used for TclExceptions.
///
/// the String object to write.
///
public void write( Interp interp, string outStr )
{
write( interp, TclString.newInstance( outStr ) );
}
/// Close the Channel. The channel is only closed, it is
/// the responsibility of the "closer" to remove the channel from
/// the channel table.
///
internal virtual void close()
{
IOException ex = null;
if ( input != null )
{
try
{
input.close();
}
catch ( IOException e )
{
ex = e;
}
input = null;
}
if ( output != null )
{
try
{
output.close();
}
catch ( IOException e )
{
ex = e;
}
output = null;
}
if ( ex != null )
throw ex;
}
/// Flush the Channel.
///
///
/// TclException is thrown when attempting to flush a
/// read only channel.
///
/// IOEcception is thrown for all other flush errors.
///
public void flush( Interp interp )
{
checkWrite( interp );
if ( output != null )
{
output.flush();
}
}
/// Move the current file pointer. If seek is not supported on the
/// given channel then -1 will be returned. A subclass should
/// override this method if it supports the seek operation.
///
///
/// currrent interpreter.
///
/// The number of bytes to move the file pointer.
///
/// where to begin incrementing the file pointer; beginning,
/// current, end.
///
public virtual void seek( Interp interp, long offset, int mode )
{
throw new TclPosixException( interp, TclPosixException.EINVAL, true, "error during seek on \"" + ChanName + "\"" );
}
/// Return the current file pointer. If tell is not supported on the
/// given channel then -1 will be returned. A subclass should override
/// this method if it supports the tell operation.
///
public virtual long tell()
{
return (long)( -1 );
}
/// Setup the TclInputStream on the first call to read
protected internal void initInput()
{
if ( input != null )
return;
input = new TclInputStream( InputStream );
input.Encoding = encoding;
input.Translation = inputTranslation;
input.EofChar = inputEofChar;
input.Buffering = buffering;
input.BufferSize = bufferSize;
input.Blocking = blocking;
}
/// Setup the TclOutputStream on the first call to write
protected internal void initOutput()
{
if ( output != null )
return;
output = new TclOutputStream( OutputStream );
output.Encoding = encoding;
output.Translation = outputTranslation;
output.EofChar = outputEofChar;
output.Buffering = buffering;
output.BufferSize = bufferSize;
output.Blocking = blocking;
}
/// Returns true if the last read reached the EOF.
public bool eof()
{
if ( input != null )
return input.eof();
else
return false;
}
// Helper methods to check read/write permission and raise a
// TclException if reading is not allowed.
protected internal void checkRead( Interp interp )
{
if ( !ReadOnly && !ReadWrite )
{
throw new TclException( interp, "channel \"" + ChanName + "\" wasn't opened for reading" );
}
}
protected internal void checkWrite( Interp interp )
{
if ( !WriteOnly && !ReadWrite )
{
throw new TclException( interp, "channel \"" + ChanName + "\" wasn't opened for writing" );
}
}
/// Tcl_InputBlocked -> isBlocked
///
/// Returns true if input is blocked on this channel, false otherwise.
///
///
public bool isBlocked( Interp interp )
{
checkRead( interp );
if ( input != null )
return input.Blocked;
else
return false;
}
/// Channel is in CRLF eol input translation mode and the last
/// byte seen was a CR.
///
public bool inputSawCR()
{
if ( input != null )
return input.sawCR();
return false;
}
}
}