#undef DEBUG
/*
* TclOutputStream.java
*
* Copyright (c) 2003 Mo DeJong
*
* 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: TclOutputStream.java,v 1.1 2003/03/08 03:42:44 mdejong Exp $
*/
// A TclOutputStream is a cross between a Java OutputStream and
// a Writer. The class supports writing raw bytes as well as
// encoded characters.
using System;
using System.IO;
using System.Text;
namespace tcl.lang
{
public class TclOutputStream
{
internal System.Text.Encoding Encoding
{
set
{
encoding = value;
}
}
internal char EofChar
{
set
{
eofChar = value;
}
}
internal int Translation
{
set
{
translation = value;
}
}
public int Buffering
{
set
{
buffering = value;
}
}
public int BufferSize
{
set
{
bufSize = value;
outputStage = null;
}
}
public bool Blocking
{
set
{
blocking = value;
}
}
public bool Blocked
{
get
{
return blocked;
}
}
/// Tcl_OutputBuffered -> getNumBufferedBytes
///
/// Return the number of bytes that are current buffered.
///
internal int NumBufferedBytes
{
get
{
ChannelBuffer buf;
int IOQueued = 0;
for ( buf = outQueueHead; buf != null; buf = buf.next )
{
IOQueued += buf.nextAdded - buf.nextRemoved;
}
if ( ( curOut != null ) && ( curOut.nextAdded > curOut.nextRemoved ) )
{
//bufferReady = true;
IOQueued += curOut.nextAdded - curOut.nextRemoved;
}
return IOQueued;
}
}
/// The Java byte stream object data will be written to.
private Stream output;
/// If nonzero, use this character as EOF marker.
private char eofChar;
/// Translation mode for end-of-line character
protected internal int translation;
/// Name of Java encoding for this Channel.
/// A null value means use no encoding (binary).
///
protected internal System.Text.Encoding encoding;
/// Current converter object. A null value means
/// that no conversions have been done yet.
///
protected internal Encoder ctb = null;
/// Buffering
protected internal int buffering;
/// Blocking
protected internal bool blocking;
/// Blocked
protected internal bool blocked = false;
/// Buffer size in bytes
protected internal int bufSize;
/// Staging area used to store chars before conversion into
/// buffered bytes.
///
protected internal char[] outputStage = null;
/// Flags used to track encoding states.
/// The encodingState member of called outputEncodingState
/// in the C ChannelState type. The encodingStart and encodingEnd
/// members combined are called outputEncodingFlags
/// and have the bit values TCL_ENCODING_END and TCL_ENCODING_START.
///
internal Object encodingState = null;
internal bool encodingStart = true;
internal bool encodingEnd = false;
/// First and last buffers in the output queue and
/// the current buffer being filled.
///
internal ChannelBuffer outQueueHead = null;
internal ChannelBuffer outQueueTail = null;
internal ChannelBuffer curOut = null;
/// Used to track buffer state, these are bit flags stored
/// in the flags filed in the C impl.
///
protected internal bool bufferReady = false;
protected internal bool bgFlushScheduled = false;
protected internal bool closed = false;
/// Posix error code of deferred error.
protected internal int unreportedError = 0;
/// FIXME: add desc
protected internal int refCount = 0;
/// Constructor for Tcl input stream class. We require
/// a byte stream source at init time, the stram can't
/// be changed after the TclInputStream is created.
///
internal TclOutputStream( Stream inOutput )
{
output = inOutput;
}
/// Tcl_Close -> close
///
/// Closes a channel.
///
/// Closes the channel if this is the last reference.
///
/// close removes the channel as far as the user is concerned.
/// However, it may continue to exist for a while longer if it has
/// a background flush scheduled. The device itself is eventually
/// closed and the channel record removed, in closeChannel.
///
internal void close()
{
//CloseCallback *cbPtr;
//Channel *chanPtr;
//ChannelState *statePtr;
int result;
// Perform special handling for standard channels being closed. If the
// refCount is now 1 it means that the last reference to the standard
// channel is being explicitly closed, so bump the refCount down
// artificially to 0. This will ensure that the channel is actually
// closed, below. Also set the static pointer to NULL for the channel.
//CheckForStdChannelsBeingClosed();
// This operation should occur at the top of a channel stack.
//chanPtr = (Channel *) chan;
//statePtr = chanPtr->state;
//chanPtr = statePtr->topChanPtr;
if ( refCount > 0 )
{
throw new TclRuntimeError( "called Tcl_Close on channel with refCount > 0" );
}
// When the channel has an escape sequence driven encoding such as
// iso2022, the terminated escape sequence must write to the buffer.
if ( ( (System.Object)encoding != null ) && ( curOut != null ) )
{
encodingEnd = true;
// FIXME : Make sure this flushes the CharToByteConverter
char[] empty = new char[0];
writeChars( empty, 0, 0 );
}
// FIXME: Impl channel close callbacks ???
//Tcl_ClearChannelHandlers(chan);
// Invoke the registered close callbacks and delete their records.
//while (statePtr->closeCbPtr != (CloseCallback *) NULL) {
// cbPtr = statePtr->closeCbPtr;
// statePtr->closeCbPtr = cbPtr->nextPtr;
// (cbPtr.proc) (cbPtr->clientData);
// ckfree((char *) cbPtr);
//}
// Ensure that the last output buffer will be flushed.
if ( ( curOut != null ) && ( curOut.nextAdded > curOut.nextRemoved ) )
{
bufferReady = true;
}
// If this channel supports it, close the read side, since we don't need it
// anymore and this will help avoid deadlocks on some channel types.
//if (chanPtr->typePtr->closeProc == TCL_CLOSE2PROC) {
// result = (chanPtr->typePtr->close2Proc)(chanPtr->instanceData, interp,
// TCL_CLOSE_READ);
//} else {
// result = 0;
//}
result = 0;
// The call to flushChannel will flush any queued output and invoke
// the close function of the channel driver, or it will set up the
// channel to be flushed and closed asynchronously.
closed = true;
if ( ( flushChannel( null, false ) != 0 ) || ( result != 0 ) )
{
// FIXME: We should raise a TclPosixException here instead
//return TCL.TCL_ERROR;
throw new IOException( "Exception in flushChannel" );
}
}
/// CloseChannel -> closeChannel
///
/// Utility procedure to close a channel and free associated resources.
///
/// If the channel was stacked, then the it will copy the necessary
/// elements of the NEXT channel into the TOP channel, in essence
/// unstacking the channel. The NEXT channel will then be freed.
///
/// If the channel was not stacked, then we will free all the bits
/// for the TOP channel, including the data structure itself.
///
/// Returns 1 if the channel was stacked, 0 otherwise.
///
protected internal int closeChannel( Interp interp, int errorCode )
{
int result = 0;
//ChannelState *statePtr; // state of the channel stack.
//ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
//if (chanPtr == NULL) {
// return result;
//}
//statePtr = chanPtr->state;
// Discard a leftover buffer in the current output buffer field.
if ( curOut != null )
{
//ckfree((char *) statePtr->curOutPtr);
curOut = null;
}
// The caller guarantees that there are no more buffers
// queued for output.
if ( outQueueHead != null )
{
throw new TclRuntimeError( "TclFlush, closed channel: queued output left" );
}
// If the EOF character is set in the channel, append that to the
// output device.
if ( eofChar != 0 )
{
try
{
output.WriteByte( (byte)eofChar );
}
catch ( IOException ex )
{
// FIXME: How can we recover here??
SupportClass.WriteStackTrace( ex, Console.Error );
}
}
// Remove this channel from of the list of all channels.
//Tcl_CutChannel((Tcl_Channel) chanPtr);
// Close and free the channel driver state.
//if (chanPtr->typePtr->closeProc != TCL_CLOSE2PROC) {
// result = (chanPtr->typePtr->closeProc)(chanPtr->instanceData, interp);
//} else {
// result = (chanPtr->typePtr->close2Proc)(chanPtr->instanceData, interp,
// 0);
//}
// Some resources can be cleared only if the bottom channel
// in a stack is closed. All the other channels in the stack
// are not allowed to remove.
//if (chanPtr == statePtr->bottomChanPtr) {
// if (statePtr->channelName != (char *) NULL) {
// ckfree((char *) statePtr->channelName);
// statePtr->channelName = NULL;
// }
// Tcl_FreeEncoding(statePtr->encoding);
// if (statePtr->outputStage != NULL) {
// ckfree((char *) statePtr->outputStage);
// statePtr->outputStage = (char *) NULL;
// }
//}
// If we are being called synchronously, report either
// any latent error on the channel or the current error.
if ( unreportedError != 0 )
{
errorCode = unreportedError;
}
if ( errorCode == 0 )
{
errorCode = result;
if ( errorCode != 0 )
{
// FIXME: How can we deal with this errno issue?
//Tcl_SetErrno(errorCode);
}
}
// Cancel any outstanding timer.
//Tcl_DeleteTimerHandler(statePtr->timer);
// Mark the channel as deleted by clearing the type structure.
//if (chanPtr->downChanPtr != (Channel *) NULL) {
// Channel *downChanPtr = chanPtr->downChanPtr;
// statePtr->nextCSPtr = tsdPtr->firstCSPtr;
// tsdPtr->firstCSPtr = statePtr;
// statePtr->topChanPtr = downChanPtr;
// downChanPtr->upChanPtr = (Channel *) NULL;
// chanPtr->typePtr = NULL;
// Tcl_EventuallyFree((ClientData) chanPtr, TCL_DYNAMIC);
// return Tcl_Close(interp, (Tcl_Channel) downChanPtr);
//}
// There is only the TOP Channel, so we free the remaining
// pointers we have and then ourselves. Since this is the
// last of the channels in the stack, make sure to free the
// ChannelState structure associated with it. We use
// Tcl_EventuallyFree to allow for any last
//chanPtr->typePtr = NULL;
//Tcl_EventuallyFree((ClientData) statePtr, TCL_DYNAMIC);
//Tcl_EventuallyFree((ClientData) chanPtr, TCL_DYNAMIC);
return errorCode;
}
/// Tcl_Flush -> flush
///
/// Flushes output data on a channel.
///
internal void flush()
{
// Force current output buffer to be output also.
if ( ( curOut != null ) && ( curOut.nextAdded > curOut.nextRemoved ) )
{
bufferReady = true;
}
int result = flushChannel( null, false );
if ( result != 0 )
{
// FIXME: Should we throw an exception here?
throw new IOException( "Exception during flushChannel" );
}
// ATK .NET has own buffer also we need to Flush the
// IO.Stream too
output.Flush();
}
/// FlushChannel -> flushChannel
///
/// This function flushes as much of the queued output as is possible
/// now. If calledFromAsyncFlush is true, it is being called in an
/// event handler to flush channel output asynchronously.
///
/// Return 0 if successful, else the error code that was returned by the
/// channel type operation.
///
/// May produce output on a channel. May block indefinitely if the
/// channel is synchronous. May schedule an async flush on the channel.
/// May recycle memory for buffers in the output queue.
///
///
/// Interp object.
///
/// True if called from an asynchronous
/// flush callback.
///
internal int flushChannel( Interp interp, bool calledFromAsyncFlush )
{
//ChannelState *statePtr = chanPtr->state;
ChannelBuffer buf;
int toWrite; // Amount of output data in current
// buffer available to be written.
int written; // Amount of output data actually
// written in current round.
int errorCode = 0; // Stores POSIX error codes from
// channel driver operations.
bool wroteSome = false; // Set to true if any data was
// written to the driver.
// Prevent writing on a dead channel -- a channel that has been closed
// but not yet deallocated. This can occur if the exit handler for the
// channel deallocation runs before all channels are deregistered in
// all interpreters.
//if (CheckForDeadChannel(interp, statePtr)) return -1;
// Loop over the queued buffers and attempt to flush as
// much as possible of the queued output to the channel.
while ( true )
{
// If the queue is empty and there is a ready current buffer, OR if
// the current buffer is full, then move the current buffer to the
// queue.
if ( ( ( curOut != null ) && ( curOut.nextAdded == curOut.bufLength ) ) || ( bufferReady && ( outQueueHead == null ) ) )
{
bufferReady = false;
curOut.next = null;
if ( outQueueHead == null )
{
outQueueHead = curOut;
}
else
{
outQueueTail.next = curOut;
}
outQueueTail = curOut;
curOut = null;
}
buf = outQueueHead;
// If we are not being called from an async flush and an async
// flush is active, we just return without producing any output.
if ( ( !calledFromAsyncFlush ) && bgFlushScheduled )
{
return 0;
}
// If the output queue is still empty, break out of the while loop.
if ( buf == null )
{
break; // Out of the "while (1)".
}
// Produce the output on the channel.
toWrite = buf.nextAdded - buf.nextRemoved;
//written = (chanPtr->typePtr->outputProc) (chanPtr->instanceData,
// bufPtr->buf + bufPtr->nextRemoved, toWrite,
// &errorCode);
try
{
output.Write( buf.buf, buf.nextRemoved, toWrite );
written = toWrite;
}
catch ( IOException ex )
{
// FIXME: How can we recover and get posix errors?
SupportClass.WriteStackTrace( ex, System.Console.Error );
errorCode = TclPosixException.EIO; // Generic I/O error ???
written = -1;
}
// If the write failed completely attempt to start the asynchronous
// flush mechanism and break out of this loop - do not attempt to
// write any more output at this time.
if ( written < 0 )
{
// If the last attempt to write was interrupted, simply retry.
if ( errorCode == TclPosixException.EINTR )
{
errorCode = 0;
continue;
}
// If the channel is non-blocking and we would have blocked,
// start a background flushing handler and break out of the loop.
if ( ( errorCode == TclPosixException.EWOULDBLOCK ) || ( errorCode == TclPosixException.EAGAIN ) )
{
// This used to check for CHANNEL_NONBLOCKING, and panic
// if the channel was blocking. However, it appears
// that setting stdin to -blocking 0 has some effect on
// the stdout when it's a tty channel (dup'ed underneath)
if ( !bgFlushScheduled )
{
bgFlushScheduled = true;
updateInterest();
}
errorCode = 0;
break;
}
// Decide whether to report the error upwards or defer it.
if ( calledFromAsyncFlush )
{
if ( unreportedError == 0 )
{
unreportedError = errorCode;
}
}
else
{
// FIXME: Need to figure out what to do here!
//Tcl_SetErrno(errorCode);
//if (interp != NULL) {
// // Casting away CONST here is safe because the
// // TCL_VOLATILE flag guarantees CONST treatment
// // of the Posix error string.
// Tcl_SetResult(interp,
// (char *) Tcl_PosixError(interp), TCL_VOLATILE);
}
// When we get an error we throw away all the output
// currently queued.
discardQueued();
continue;
}
else
{
wroteSome = true;
}
buf.nextRemoved += written;
// If this buffer is now empty, recycle it.
if ( buf.nextRemoved == buf.nextAdded )
{
outQueueHead = buf.next;
if ( outQueueHead == null )
{
outQueueTail = null;
}
recycleBuffer( buf, false );
}
} // Closes "while (1)".
// If we wrote some data while flushing in the background, we are done.
// We can't finish the background flush until we run out of data and
// the channel becomes writable again. This ensures that all of the
// pending data has been flushed at the system level.
if ( bgFlushScheduled )
{
if ( wroteSome )
{
return errorCode;
}
else if ( outQueueHead == null )
{
bgFlushScheduled = false;
// FIXME: What is this watchProc?
//(chanPtr->typePtr->watchProc)(chanPtr->instanceData,
// statePtr->interestMask);
}
}
// If the channel is flagged as closed, delete it when the refCount
// drops to zero, the output queue is empty and there is no output
// in the current output buffer.
if ( closed && ( refCount <= 0 ) && ( outQueueHead == null ) && ( ( curOut == null ) || ( curOut.nextAdded == curOut.nextRemoved ) ) )
{
return closeChannel( interp, errorCode );
}
return errorCode;
}
// Helper class to implement integer pass by reference.
public class IntPtr
{
private void InitBlock( TclOutputStream enclosingInstance )
{
this.enclosingInstance = enclosingInstance;
}
private TclOutputStream enclosingInstance;
public TclOutputStream Enclosing_Instance
{
get
{
return enclosingInstance;
}
}
internal int i;
internal IntPtr( TclOutputStream enclosingInstance )
{
InitBlock( enclosingInstance );
}
internal IntPtr( TclOutputStream enclosingInstance, int value )
{
InitBlock( enclosingInstance );
i = value;
}
}
/// RecycleBuffer -> recycleBuffer
///
/// Helper function to recycle output buffers. Ensures that
/// that curOut is set to a buffer. Only if these conditions
/// are met is the buffer released so that it can be
/// garbage collected.
///
private void recycleBuffer( ChannelBuffer buf, bool mustDiscard )
{
if ( mustDiscard )
return;
// Only save buffers which are at least as big as the requested
// buffersize for the channel. This is to honor dynamic changes
// of the buffersize made by the user.
if ( ( buf.bufLength - tcl.lang.ChannelBuffer.BUFFER_PADDING ) < bufSize )
{
return;
}
if ( curOut == null )
{
curOut = buf;
buf.nextRemoved = tcl.lang.ChannelBuffer.BUFFER_PADDING;
buf.nextAdded = tcl.lang.ChannelBuffer.BUFFER_PADDING;
buf.next = null;
}
}
/// DiscardOutputQueued -> discardQueued
///
/// Discards all output queued in the output queue of a channel.
///
private void discardQueued()
{
ChannelBuffer buf;
while ( outQueueHead != null )
{
buf = outQueueHead;
outQueueHead = buf.next;
recycleBuffer( buf, false );
}
outQueueHead = null;
outQueueTail = null;
}
/// UpdateInterest -> updateInterest
///
/// Arrange for the notifier to call us back at appropriate times
/// based on the current state of the channel.
///
internal void updateInterest()
{
// FIXME: Currently unimplemented
}
/// seekCheckBuferReady
///
/// This method is used by the seek command to check
/// the channel for buffered output and mark the
/// buffer as ready to flush if found.
///
internal void seekCheckBuferReady()
{
if ( ( curOut != null ) && ( curOut.nextAdded > curOut.nextRemoved ) )
{
bufferReady = true;
}
}
/// TranslateOutputEOL -> translateEOL
///
/// Helper function for writeBytes() and writeChars(). Converts the
/// '\n' characters in the source buffer into the appropriate EOL
/// form specified by the output translation mode.
///
/// EOL translation stops either when the source buffer is empty
/// or the output buffer is full.
///
/// When converting to CRLF mode and there is only 1 byte left in
/// the output buffer, this routine stores the '\r' in the last
/// byte and then stores the '\n' in the byte just past the end of the
/// buffer. The caller is responsible for passing in a buffer that
/// is large enough to hold the extra byte.
///
/// Results:
///
/// The return value is 1 if a '\n' was translated from the source
/// buffer, or 0 otherwise -- this can be used by the caller to
/// decide to flush a line-based channel even though the channel
/// buffer is not full.
///
/// dstLenPtr.i is filled with how many bytes of the output buffer
/// were used. As mentioned above, this can be one more that
/// the output buffer's specified length if a CRLF was stored.
///
/// srcLenPtr.i is filled with how many bytes of the source buffer
/// were consumed.
///
/// It may be obvious, but bears mentioning that when converting
/// in CRLF mode (which requires two bytes of storage in the output
/// buffer), the number of bytes consumed from the source buffer
/// will be less than the number of bytes stored in the output buffer.
///
///
/// Output buffer to fill with translated bytes or chars.
///
/// First unused index in the dst output array.
///
/// Input buffer that holds the bytes or chars to translate
///
/// Index of first available byte in src array.
///
/// On entry, the maximum length of output
/// buffer in bytes or chars. On exit, the number of
/// bytes or chars actually used in output buffer.
///
/// On entry, the length of source buffer.
/// On exit, the number of bytes or chars read from
/// the source buffer.
///
internal bool translateEOL( System.Object dstArray, int dstStart, Object srcArray, int srcStart, IntPtr dstLenPtr, IntPtr srcLenPtr )
{
// Figure out if the srcArray and dstArray buffers
// are byte or char arrays.
bool isCharType;
char[] srcArrayChar, dstArrayChar;
byte[] srcArrayByte, dstArrayByte;
if ( ( srcArray is char[] ) && ( dstArray is char[] ) )
{
isCharType = true;
srcArrayChar = (char[])srcArray;
dstArrayChar = (char[])dstArray;
srcArrayByte = null;
dstArrayByte = null;
}
else if ( ( srcArray is byte[] ) && ( dstArray is byte[] ) )
{
isCharType = false;
srcArrayChar = null;
dstArrayChar = null;
srcArrayByte = (byte[])srcArray;
dstArrayByte = (byte[])dstArray;
}
else
{
throw new TclRuntimeError( "unknown array argument types" );
}
int src, dst, dstEnd, srcLen;
bool newlineFound;
src = srcStart;
dst = dstStart;
newlineFound = false;
srcLen = srcLenPtr.i;
switch ( translation )
{
case TclIO.TRANS_LF:
{
if ( isCharType )
{
for ( dstEnd = dst + srcLen; dst < dstEnd; )
{
if ( srcArrayChar[src] == '\n' )
{
newlineFound = true;
}
dstArrayChar[dst++] = srcArrayChar[src++];
}
}
else
{
for ( dstEnd = dst + srcLen; dst < dstEnd; )
{
if ( srcArrayByte[src] == '\n' )
{
newlineFound = true;
}
dstArrayByte[dst++] = srcArrayByte[src++];
}
}
dstLenPtr.i = srcLen;
break;
}
case TclIO.TRANS_CR:
{
if ( isCharType )
{
for ( dstEnd = dst + srcLen; dst < dstEnd; )
{
if ( srcArrayChar[src] == '\n' )
{
dstArrayChar[dst++] = '\r';
newlineFound = true;
src++;
}
else
{
dstArrayChar[dst++] = srcArrayChar[src++];
}
}
}
else
{
for ( dstEnd = dst + srcLen; dst < dstEnd; )
{
if ( srcArrayByte[src] == '\n' )
{
dstArrayByte[dst++] = (byte)SupportClass.Identity( '\r' );
newlineFound = true;
src++;
}
else
{
dstArrayByte[dst++] = srcArrayByte[src++];
}
}
}
dstLenPtr.i = srcLen;
break;
}
case TclIO.TRANS_CRLF:
{
// Since this causes the number of bytes to grow, we
// start off trying to put 'srcLen' bytes into the
// output buffer, but allow it to store more bytes, as
// long as there's still source bytes and room in the
// output buffer.
int dstMax;
//int dstStart, srcStart;
//dstStart = dst;
dstMax = dst + dstLenPtr.i;
//srcStart = src;
if ( srcLen < dstLenPtr.i )
{
dstEnd = dst + srcLen;
}
else
{
dstEnd = dst + dstLenPtr.i;
}
if ( isCharType )
{
while ( dst < dstEnd )
{
if ( srcArrayChar[src] == '\n' )
{
if ( dstEnd < dstMax )
{
dstEnd++;
}
dstArrayChar[dst++] = '\r';
newlineFound = true;
}
dstArrayChar[dst++] = srcArrayChar[src++];
}
}
else
{
while ( dst < dstEnd )
{
if ( srcArrayByte[src] == '\n' )
{
if ( dstEnd < dstMax )
{
dstEnd++;
}
dstArrayByte[dst++] = (byte)SupportClass.Identity( '\r' );
newlineFound = true;
}
dstArrayByte[dst++] = srcArrayByte[src++];
}
}
srcLenPtr.i = src - srcStart;
dstLenPtr.i = dst - dstStart;
break;
}
default:
{
break;
}
}
return newlineFound;
}
/// Tcl_UtfToExternal -> unicodeToExternal
///
/// Convert a source buffer from unicode characters to a specified encoding.
///
/// FIXME: Add doc for return values
///
///
/// Source characters.
///
/// First index in src input array.
///
/// Number of characters in src buffer.
///
/// Array to store encoded bytes in.
///
/// First available index in dst array.
///
/// Length of dst array.
///
/// Filled with the number of characters from
/// the source string that were converted.
/// This may be less than the original source
/// length if there was a problem converting
/// some source characters.
///
/// Filled with the number of bytes that were
/// stored in the output buffer as a result of
/// the conversion
///
/// Filled with the number of characters that
/// correspond to the bytes stored in the
/// output buffer.
///
internal int unicodeToExternal( char[] src, int srcOff, int srcLen, byte[] dst, int dstOff, int dstLen, IntPtr srcReadPtr, IntPtr dstWrotePtr, IntPtr dstCharsPtr )
{
bool debug;
int result;
if ( (System.Object)encoding == null )
{
throw new TclRuntimeError( "unicodeToExternal called with null encoding" );
}
if ( srcLen == 0 )
{
srcReadPtr.i = 0;
if ( dstWrotePtr != null )
dstWrotePtr.i = 0;
if ( dstCharsPtr != null )
dstCharsPtr.i = 0;
return 0;
}
#if DEBUG
System.Diagnostics.Debug.WriteLine("now to encode char array of length " + srcLen);
System.Diagnostics.Debug.WriteLine("srcOff is " + srcOff);
for (int i = srcOff; i < (srcOff + srcLen); i++)
{
System.Diagnostics.Debug.WriteLine("(char) '" + src[i] + "'");
}
System.Diagnostics.Debug.WriteLine("encoded as " + encoding);
#endif
if ( ctb == null )
{
try
{
ctb = this.encoding.GetEncoder();
}
catch ( IOException ex )
{
// Valid encodings should be checked already
throw new TclRuntimeError( "unsupported encoding \"" + encoding + "\"" );
}
}
int chars_read, bytes_written;
int required_bytes = ctb.GetByteCount( src, srcOff, srcLen, false );
// ATK do not allow buffer overflow by decresing read bytes count
if ( required_bytes > dstLen )
{
srcLen = dstLen;
}
bytes_written = ctb.GetBytes( src, srcOff, srcLen, dst, dstOff, false );
srcReadPtr.i = srcLen;
if ( dstWrotePtr != null )
dstWrotePtr.i = bytes_written;
if ( dstCharsPtr != null )
dstCharsPtr.i = srcLen;
// FIXME: When do we return error codes?
result = 0;
return result;
}
/// WriteBytes -> writeBytes
///
/// Write a sequence of bytes into an output buffer, may queue the
/// buffer for output if it gets full, and also remembers whether the
/// current buffer is ready e.g. if it contains a newline and we are in
/// line buffering mode.
///
/// The number of bytes written or -1 in case of error. If -1,
/// Tcl_GetErrno will return the error code.
///
/// May buffer up output and may cause output to be produced on the
/// channel.
///
///
/// Bytes to write.
///
/// First index in src array.
///
/// Number of bytes to write.
///
internal int writeBytes( byte[] srcArray, int srcOff, int srcLen )
{
ChannelBuffer buf;
byte[] dstArray;
int dst, src, dstMax, sawLF, total, savedLF;
IntPtr dstLen = new IntPtr( this );
IntPtr toWrite = new IntPtr( this );
total = 0;
sawLF = 0;
savedLF = 0;
src = srcOff;
// Loop over all bytes in src, storing them in output buffer with
// proper EOL translation.
while ( srcLen + savedLF > 0 )
{
buf = curOut;
if ( buf == null )
{
buf = new ChannelBuffer( bufSize );
curOut = buf;
}
//dst = bufPtr->buf + bufPtr->nextAdded;
dstArray = buf.buf;
dst = buf.nextAdded;
dstMax = buf.bufLength - buf.nextAdded;
dstLen.i = dstMax;
toWrite.i = dstLen.i;
if ( toWrite.i > srcLen )
{
toWrite.i = srcLen;
}
if ( savedLF != 0 )
{
// A '\n' was left over from last call to translateEOL()
// and we need to store it in this buffer. If the channel is
// line-based, we will need to flush it.
dstArray[dst++] = (byte)SupportClass.Identity( '\n' );
dstLen.i--;
sawLF++;
}
if ( translateEOL( dstArray, dst, srcArray, src, dstLen, toWrite ) )
{
sawLF++;
}
dstLen.i += savedLF;
savedLF = 0;
if ( dstLen.i > dstMax )
{
savedLF = 1;
dstLen.i = dstMax;
}
buf.nextAdded += dstLen.i;
if ( checkFlush( buf, ( sawLF != 0 ) ) != 0 )
{
return -1;
}
total += dstLen.i;
src += toWrite.i;
srcLen -= toWrite.i;
sawLF = 0;
}
return total;
}
/// CheckFlush -> checkFlush
///
/// Helper function for writeBytes() and writeChars(). If the
/// channel buffer is ready to be flushed, flush it.
///
/// The return value is -1 if there was a problem flushing the
/// channel buffer, or 0 otherwise.
///
/// The buffer will be recycled if it is flushed.
///
///
/// Channel buffer to possibly flush.
///
/// True if a the channel buffer
/// contains a newline.
///
internal int checkFlush( ChannelBuffer buf, bool newlineFlag )
{
// The current buffer is ready for output:
// 1. if it is full.
// 2. if it contains a newline and this channel is line-buffered.
// 3. if it contains any output and this channel is unbuffered.
if ( !bufferReady )
{
if ( buf.nextAdded == buf.bufLength )
{
bufferReady = true;
}
else if ( buffering == TclIO.BUFF_LINE )
{
if ( newlineFlag )
{
bufferReady = true;
}
}
else if ( buffering == TclIO.BUFF_NONE )
{
bufferReady = true;
}
}
if ( bufferReady )
{
if ( flushChannel( null, false ) != 0 )
{
return -1;
}
}
return 0;
}
/// WriteChars -> writeChars
///
/// Convert chars to the channel's external encoding and
/// write the produced bytes into an output buffer, may queue the
/// buffer for output if it gets full, and also remembers whether the
/// current buffer is ready e.g. if it contains a newline and we are in
/// line buffering mode.
///
/// The number of bytes written or -1 in case of error. If -1,
/// Tcl_GetErrno will return the error code.
///
/// May buffer up output and may cause output to be produced on the
/// channel.
///
///
/// Chars to write.
///
/// First index in src array.
///
/// Number of chars to write.
///
internal int writeChars( char[] srcArray, int srcOff, int srcLen )
{
//ChannelState *statePtr = chanPtr->state; // state info for channel
ChannelBuffer buf;
char[] stageArray;
byte[] dstArray;
int stage, src, dst;
int saved, savedLF, sawLF, total, dstLen, stageMax;
int endEncoding, result;
bool consumedSomething;
//Tcl_Encoding encoding;
byte[] safe = new byte[ChannelBuffer.BUFFER_PADDING];
IntPtr stageLen = new IntPtr( this );
IntPtr toWrite = new IntPtr( this );
IntPtr stageRead = new IntPtr( this );
IntPtr dstWrote = new IntPtr( this );
total = 0;
sawLF = 0;
savedLF = 0;
saved = 0;
//encoding = statePtr->encoding;
src = 0;
// Write the terminated escape sequence even if srcLen is 0.
endEncoding = ( encodingEnd ? 0 : 1 );
// Loop over all characters in src, storing them in staging buffer
// with proper EOL translation.
consumedSomething = true;
while ( consumedSomething && ( srcLen + savedLF + endEncoding > 0 ) )
{
consumedSomething = false;
if ( outputStage == null )
{
outputStage = new char[bufSize + 2];
}
stageArray = outputStage;
stage = 0;
stageMax = bufSize;
stageLen.i = stageMax;
toWrite.i = stageLen.i;
if ( toWrite.i > srcLen )
{
toWrite.i = srcLen;
}
if ( savedLF != 0 )
{
// A '\n' was left over from last call to TranslateOutputEOL()
// and we need to store it in the staging buffer. If the
// channel is line-based, we will need to flush the output
// buffer (after translating the staging buffer).
stageArray[stage++] = '\n';
stageLen.i--;
sawLF++;
}
if ( translateEOL( stageArray, stage, srcArray, src, stageLen, toWrite ) )
{
sawLF++;
}
stage -= savedLF;
stageLen.i += savedLF;
savedLF = 0;
if ( stageLen.i > stageMax )
{
savedLF = 1;
stageLen.i = stageMax;
}
src += toWrite.i;
srcLen -= toWrite.i;
// Loop over all characters in staging buffer, converting them
// to external encoding, storing them in output buffer.
while ( stageLen.i + saved + endEncoding > 0 )
{
buf = curOut;
if ( buf == null )
{
buf = new ChannelBuffer( bufSize );
curOut = buf;
}
// dst = buf.buf + buf.nextAdded;
dstArray = buf.buf;
dst = buf.nextAdded;
dstLen = buf.bufLength - buf.nextAdded;
if ( saved != 0 )
{
// Here's some translated bytes left over from the last
// buffer that we need to stick at the beginning of this
// buffer.
Array.Copy( safe, 0, dstArray, dst, saved );
buf.nextAdded += saved;
dst += saved;
dstLen -= saved;
saved = 0;
}
result = unicodeToExternal( stageArray, stage, stageLen.i, dstArray, dst, dstLen + ChannelBuffer.BUFFER_PADDING, stageRead, dstWrote, null );
// FIXME: Not clear how this condition is dealt with.
//
// Fix for SF #506297, reported by Martin Forssen
// .
//
// The encoding chosen in the script exposing the bug writes out
// three intro characters when TCL_ENCODING_START is set, but does
// not consume any input as TCL_ENCODING_END is cleared. As some
// output was generated the enclosing loop calls UtfToExternal
// again, again with START set. Three more characters in the out
// and still no use of input ... To break this infinite loop we
// remove TCL_ENCODING_START from the set of flags after the first
// call (no condition is required, the later calls remove an unset
// flag, which is a no-op). This causes the subsequent calls to
// UtfToExternal to consume and convert the actual input.
encodingStart = false;
// The following can never happen since we use unicode characters.
//
//if ((result != 0) && ((stageRead.i + dstWrote.i) == 0)) {
// // We have an incomplete UTF-8 character at the end of the
// // staging buffer. It will get moved to the beginning of the
// // staging buffer followed by more bytes from src.
//
// src -= stageLen.i;
// srcLen += stageLen.i;
// stageLen.i = 0;
// savedLF = 0;
// break;
//}
buf.nextAdded += dstWrote.i;
if ( buf.nextAdded > buf.bufLength )
{
// When translating from unicode to external encoding, we
// allowed the translation to produce a character that
// crossed the end of the output buffer, so that we would
// get a completely full buffer before flushing it. The
// extra bytes will be moved to the beginning of the next
// buffer.
saved = buf.nextAdded - buf.bufLength;
// ATK Array.Copy(SupportClass.ToByteArray((System.Array) dstArray), dst + dstLen, SupportClass.ToByteArray(safe), 0, saved);
Array.Copy( dstArray, dst + dstLen, safe, 0, saved );
buf.nextAdded = buf.bufLength;
}
if ( checkFlush( buf, ( sawLF != 0 ) ) != 0 )
{
return -1;
}
total += dstWrote.i;
stage += stageRead.i;
stageLen.i -= stageRead.i;
sawLF = 0;
consumedSomething = true;
// If all translated characters are written to the buffer,
// endEncoding is set to 0 because the escape sequence may be
// output.
if ( ( stageLen.i + saved == 0 ) && ( result == 0 ) )
{
endEncoding = 0;
}
}
}
// If nothing was written and it happened because there was no progress
// in the UTF conversion, we throw an error.
if ( !consumedSomething && ( total == 0 ) )
{
//Tcl_SetErrno (EINVAL);
return -1;
}
return total;
}
/// DoWriteChars -> doWriteChars
///
/// Takes a sequence of characters and converts them for output
/// using the channel's current encoding, may queue the buffer for
/// output if it gets full, and also remembers whether the current
/// buffer is ready e.g. if it contains a newline and we are in
/// line buffering mode. Compensates stacking, i.e. will redirect the
/// data from the specified channel to the topmost channel in a stack.
///
/// The number of bytes written or -1 in case of error. If -1,
/// Tcl_GetErrno will return the error code.
///
/// May buffer up output and may cause output to be produced on the
/// channel.
///
///
/// Chars to write.
///
/// First index in src array.
///
/// Number of chars to write.
///
internal int doWriteChars( char[] src, int srcOff, int srcLen )
{
// HACK ATK Was soll das?
return -1;
}
/// Tcl_WriteObj -> writeObj
///
/// Takes the Tcl object and queues its contents for output. If the
/// encoding of the channel is NULL, takes the byte-array representation
/// of the object and queues those bytes for output. Otherwise, takes
/// the characters in the UTF-8 (string) representation of the object
/// and converts them for output using the channel's current encoding.
/// May flush internal buffers to output if one becomes full or is ready
/// for some other reason, e.g. if it contains a newline and the channel
/// is in line buffering mode.
///
/// The number of bytes written or -1 in case of error. If -1,
/// Tcl_GetErrno will return the error code.
///
/// May buffer up output and may cause output to be produced on the
/// channel.
///
///
/// The object to write.
///
internal int writeObj( TclObject obj )
{
// Always use the topmost channel of the stack
//char *src;
int srcLen;
//statePtr = ((Channel *) chan)->state;
//chanPtr = statePtr->topChanPtr;
//if (CheckChannelErrors(statePtr, TCL_WRITABLE) != 0) {
// return -1;
//}
if ( (System.Object)encoding == null )
{
srcLen = TclByteArray.getLength( null, obj );
byte[] bytes = TclByteArray.getBytes( null, obj );
return writeBytes( bytes, 0, srcLen );
}
else
{
char[] chars = obj.ToString().ToCharArray();
return writeChars( chars, 0, chars.Length );
}
}
}
}