0001: /*
0002: * TclOutputStream.java
0003: *
0004: * Copyright (c) 2003 Mo DeJong
0005: *
0006: * See the file "license.terms" for information on usage and
0007: * redistribution of this file, and for a DISCLAIMER OF ALL
0008: * WARRANTIES.
0009: *
0010: * RCS: @(#) $Id: TclOutputStream.java,v 1.4 2006/07/07 23:36:00 mdejong Exp $
0011: */
0012:
0013: // A TclOutputStream is a cross between a Java OutputStream and
0014: // a Writer. The class supports writing raw bytes as well as
0015: // encoded characters.
0016: package tcl.lang;
0017:
0018: import java.io.IOException;
0019: import java.io.EOFException;
0020: import java.io.OutputStream;
0021: import java.io.FileOutputStream;
0022: import java.io.UnsupportedEncodingException;
0023:
0024: import java.nio.CharBuffer;
0025: import java.nio.ByteBuffer;
0026:
0027: import java.nio.charset.Charset;
0028: import java.nio.charset.CharsetEncoder;
0029: import java.nio.charset.CoderResult;
0030: import java.nio.charset.IllegalCharsetNameException;
0031: import java.nio.charset.UnsupportedCharsetException;
0032:
0033: class TclOutputStream {
0034:
0035: /**
0036: * The Java byte stream object data will be written to.
0037: */
0038:
0039: private OutputStream output;
0040:
0041: /**
0042: * If nonzero, use this character as EOF marker.
0043: */
0044:
0045: private char eofChar;
0046:
0047: /**
0048: * Translation mode for end-of-line character
0049: */
0050:
0051: protected int translation;
0052:
0053: /**
0054: * Name of Java encoding for this Channel.
0055: * A null value means use no encoding (binary).
0056: */
0057:
0058: protected String encoding;
0059:
0060: /**
0061: * Charset encoder object. A null value means
0062: * that no conversions have been done yet.
0063: */
0064:
0065: protected CharsetEncoder cse = null;
0066:
0067: /**
0068: * Buffering
0069: */
0070:
0071: protected int buffering;
0072:
0073: /**
0074: * Blocking
0075: */
0076:
0077: protected boolean blocking;
0078:
0079: /**
0080: * Blocked
0081: */
0082:
0083: protected boolean blocked = false;
0084:
0085: /**
0086: * Buffer size in bytes
0087: */
0088:
0089: protected int bufSize;
0090:
0091: /**
0092: * Staging area used to store chars before conversion into
0093: * buffered bytes.
0094: */
0095:
0096: protected char[] outputStage = null;
0097:
0098: /**
0099: * Flags used to track encoding states.
0100: * The encodingState member of called outputEncodingState
0101: * in the C ChannelState type. The encodingStart and encodingEnd
0102: * members combined are called outputEncodingFlags
0103: * and have the bit values TCL_ENCODING_END and TCL_ENCODING_START.
0104: */
0105:
0106: Object encodingState = null;
0107: boolean encodingStart = true;
0108: boolean encodingEnd = false;
0109:
0110: /**
0111: * First and last buffers in the output queue and
0112: * the current buffer being filled.
0113: */
0114:
0115: ChannelBuffer outQueueHead = null;
0116: ChannelBuffer outQueueTail = null;
0117: ChannelBuffer curOut = null;
0118:
0119: /**
0120: * Used to track buffer state, these are bit flags stored
0121: * in the flags filed in the C impl.
0122: */
0123:
0124: protected boolean bufferReady = false;
0125: protected boolean bgFlushScheduled = false;
0126: protected boolean closed = false;
0127:
0128: /**
0129: * Posix error code of deferred error.
0130: */
0131: protected int unreportedError = 0;
0132:
0133: /**
0134: * FIXME: add desc
0135: */
0136:
0137: protected int refCount = 0;
0138:
0139: /**
0140: * This flag is true when the OutputStream is from
0141: * a file on disk that should be sync()'ed after
0142: * a flush. A sync should not be used for non-file
0143: * streams.
0144: */
0145:
0146: protected boolean canSync = false;
0147:
0148: /**
0149: * Constructor for Tcl input stream class. We require
0150: * a byte stream source at init time, the stram can't
0151: * be changed after the TclInputStream is created.
0152: */
0153:
0154: TclOutputStream(OutputStream inOutput) {
0155: output = inOutput;
0156: }
0157:
0158: /**
0159: * Set the sync flag for a channel so that a
0160: * sync will be invoked in addition to a flush.
0161: */
0162:
0163: void setSync(boolean canSync) {
0164: this .canSync = canSync;
0165: }
0166:
0167: /**
0168: * Tcl_Close -> close
0169: *
0170: * Closes a channel.
0171: *
0172: * Closes the channel if this is the last reference.
0173: *
0174: * close removes the channel as far as the user is concerned.
0175: * However, it may continue to exist for a while longer if it has
0176: * a background flush scheduled. The device itself is eventually
0177: * closed and the channel record removed, in closeChannel.
0178: */
0179:
0180: void close() throws IOException {
0181: //CloseCallback *cbPtr;
0182: //Channel *chanPtr;
0183: //ChannelState *statePtr;
0184: int result;
0185:
0186: // Perform special handling for standard channels being closed. If the
0187: // refCount is now 1 it means that the last reference to the standard
0188: // channel is being explicitly closed, so bump the refCount down
0189: // artificially to 0. This will ensure that the channel is actually
0190: // closed, below. Also set the static pointer to NULL for the channel.
0191:
0192: //CheckForStdChannelsBeingClosed();
0193:
0194: // This operation should occur at the top of a channel stack.
0195:
0196: //chanPtr = (Channel *) chan;
0197: //statePtr = chanPtr->state;
0198: //chanPtr = statePtr->topChanPtr;
0199:
0200: if (refCount > 0) {
0201: throw new TclRuntimeError(
0202: "called Tcl_Close on channel with refCount > 0");
0203: }
0204:
0205: // When the channel has an escape sequence driven encoding such as
0206: // iso2022, the terminated escape sequence must write to the buffer.
0207:
0208: if ((encoding != null) && (curOut != null)
0209: /*&& (CheckChannelErrors(statePtr, TCL_WRITABLE) == 0)*/) {
0210: encodingEnd = true;
0211: char[] empty = new char[0];
0212: writeChars(empty, 0, 0);
0213: }
0214:
0215: // FIXME: Impl channel close callbacks ???
0216: //Tcl_ClearChannelHandlers(chan);
0217:
0218: // Invoke the registered close callbacks and delete their records.
0219:
0220: //while (statePtr->closeCbPtr != (CloseCallback *) NULL) {
0221: // cbPtr = statePtr->closeCbPtr;
0222: // statePtr->closeCbPtr = cbPtr->nextPtr;
0223: // (cbPtr->proc) (cbPtr->clientData);
0224: // ckfree((char *) cbPtr);
0225: //}
0226:
0227: // Ensure that the last output buffer will be flushed.
0228:
0229: if ((curOut != null) && (curOut.nextAdded > curOut.nextRemoved)) {
0230: bufferReady = true;
0231: }
0232:
0233: // If this channel supports it, close the read side, since we don't need it
0234: // anymore and this will help avoid deadlocks on some channel types.
0235:
0236: //if (chanPtr->typePtr->closeProc == TCL_CLOSE2PROC) {
0237: // result = (chanPtr->typePtr->close2Proc)(chanPtr->instanceData, interp,
0238: // TCL_CLOSE_READ);
0239: //} else {
0240: // result = 0;
0241: //}
0242: result = 0;
0243:
0244: // The call to flushChannel will flush any queued output and invoke
0245: // the close function of the channel driver, or it will set up the
0246: // channel to be flushed and closed asynchronously.
0247:
0248: closed = true;
0249: if ((flushChannel(/*interp*/null, false) != 0)
0250: || (result != 0)) {
0251: // FIXME: We should raise a TclPosixException here instead
0252: //return TCL_ERROR;
0253: throw new IOException("Exception in flushChannel");
0254: }
0255: }
0256:
0257: /**
0258: * CloseChannel -> closeChannel
0259: *
0260: * Utility procedure to close a channel and free associated resources.
0261: *
0262: * If the channel was stacked, then the it will copy the necessary
0263: * elements of the NEXT channel into the TOP channel, in essence
0264: * unstacking the channel. The NEXT channel will then be freed.
0265: *
0266: * If the channel was not stacked, then we will free all the bits
0267: * for the TOP channel, including the data structure itself.
0268: *
0269: * Returns 1 if the channel was stacked, 0 otherwise.
0270: */
0271:
0272: protected int closeChannel(Interp interp, int errorCode)
0273: throws IOException {
0274: int result = 0;
0275: //ChannelState *statePtr; // state of the channel stack.
0276: //ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
0277:
0278: //if (chanPtr == NULL) {
0279: // return result;
0280: //}
0281: //statePtr = chanPtr->state;
0282:
0283: // Discard a leftover buffer in the current output buffer field.
0284:
0285: if (curOut != null) {
0286: //ckfree((char *) statePtr->curOutPtr);
0287: curOut = null;
0288: }
0289:
0290: // The caller guarantees that there are no more buffers
0291: // queued for output.
0292:
0293: if (outQueueHead != null) {
0294: throw new TclRuntimeError(
0295: "TclFlush, closed channel: queued output left");
0296: }
0297:
0298: // If the EOF character is set in the channel, append that to the
0299: // output device.
0300:
0301: if (eofChar != 0) {
0302: try {
0303: output.write((byte) eofChar);
0304: } catch (IOException ex) {
0305: // FIXME: How can we recover here??
0306: ex.printStackTrace(System.err);
0307: }
0308: }
0309:
0310: // Remove this channel from of the list of all channels.
0311:
0312: //Tcl_CutChannel((Tcl_Channel) chanPtr);
0313:
0314: // Close and free the channel driver state.
0315:
0316: //if (chanPtr->typePtr->closeProc != TCL_CLOSE2PROC) {
0317: // result = (chanPtr->typePtr->closeProc)(chanPtr->instanceData, interp);
0318: //} else {
0319: // result = (chanPtr->typePtr->close2Proc)(chanPtr->instanceData, interp,
0320: // 0);
0321: //}
0322:
0323: // Some resources can be cleared only if the bottom channel
0324: // in a stack is closed. All the other channels in the stack
0325: // are not allowed to remove.
0326:
0327: //if (chanPtr == statePtr->bottomChanPtr) {
0328: // if (statePtr->channelName != (char *) NULL) {
0329: // ckfree((char *) statePtr->channelName);
0330: // statePtr->channelName = NULL;
0331: // }
0332:
0333: // Tcl_FreeEncoding(statePtr->encoding);
0334: // if (statePtr->outputStage != NULL) {
0335: // ckfree((char *) statePtr->outputStage);
0336: // statePtr->outputStage = (char *) NULL;
0337: // }
0338: //}
0339:
0340: // If we are being called synchronously, report either
0341: // any latent error on the channel or the current error.
0342:
0343: if (unreportedError != 0) {
0344: errorCode = unreportedError;
0345: }
0346: if (errorCode == 0) {
0347: errorCode = result;
0348: if (errorCode != 0) {
0349: // FIXME: How can we deal with this errno issue?
0350: //Tcl_SetErrno(errorCode);
0351: }
0352: }
0353:
0354: // Cancel any outstanding timer.
0355:
0356: //Tcl_DeleteTimerHandler(statePtr->timer);
0357:
0358: // Mark the channel as deleted by clearing the type structure.
0359:
0360: //if (chanPtr->downChanPtr != (Channel *) NULL) {
0361: // Channel *downChanPtr = chanPtr->downChanPtr;
0362:
0363: // statePtr->nextCSPtr = tsdPtr->firstCSPtr;
0364: // tsdPtr->firstCSPtr = statePtr;
0365:
0366: // statePtr->topChanPtr = downChanPtr;
0367: // downChanPtr->upChanPtr = (Channel *) NULL;
0368: // chanPtr->typePtr = NULL;
0369:
0370: // Tcl_EventuallyFree((ClientData) chanPtr, TCL_DYNAMIC);
0371: // return Tcl_Close(interp, (Tcl_Channel) downChanPtr);
0372: //}
0373:
0374: // There is only the TOP Channel, so we free the remaining
0375: // pointers we have and then ourselves. Since this is the
0376: // last of the channels in the stack, make sure to free the
0377: // ChannelState structure associated with it. We use
0378: // Tcl_EventuallyFree to allow for any last
0379:
0380: //chanPtr->typePtr = NULL;
0381:
0382: //Tcl_EventuallyFree((ClientData) statePtr, TCL_DYNAMIC);
0383: //Tcl_EventuallyFree((ClientData) chanPtr, TCL_DYNAMIC);
0384:
0385: return errorCode;
0386: }
0387:
0388: /**
0389: * Tcl_Flush -> flush
0390: *
0391: * Flushes output data on a channel.
0392: */
0393:
0394: void flush() throws IOException {
0395: // Force current output buffer to be output also.
0396:
0397: if ((curOut != null) && (curOut.nextAdded > curOut.nextRemoved)) {
0398: bufferReady = true;
0399: }
0400:
0401: int result = flushChannel(null, false);
0402: if (result != 0) {
0403: // FIXME: Should we throw an exception here?
0404: throw new IOException("Exception during flushChannel");
0405: }
0406: }
0407:
0408: /**
0409: * FlushChannel -> flushChannel
0410: *
0411: * This function flushes as much of the queued output as is possible
0412: * now. If calledFromAsyncFlush is true, it is being called in an
0413: * event handler to flush channel output asynchronously.
0414: *
0415: * Return 0 if successful, else the error code that was returned by the
0416: * channel type operation.
0417: *
0418: * May produce output on a channel. May block indefinitely if the
0419: * channel is synchronous. May schedule an async flush on the channel.
0420: * May recycle memory for buffers in the output queue.
0421: *
0422: * @param interp Interp object.
0423: * @param calledFromAsyncFlush True if called from an asynchronous
0424: * flush callback.
0425: */
0426:
0427: int flushChannel(Interp interp, boolean calledFromAsyncFlush)
0428: throws IOException {
0429: //ChannelState *statePtr = chanPtr->state;
0430: ChannelBuffer buf;
0431: int toWrite; // Amount of output data in current
0432: // buffer available to be written.
0433: int written; // Amount of output data actually
0434: // written in current round.
0435: int errorCode = 0; // Stores POSIX error codes from
0436: // channel driver operations.
0437: boolean wroteSome = false; // Set to true if any data was
0438: // written to the driver.
0439:
0440: try {
0441:
0442: // Prevent writing on a dead channel -- a channel that has been closed
0443: // but not yet deallocated. This can occur if the exit handler for the
0444: // channel deallocation runs before all channels are deregistered in
0445: // all interpreters.
0446:
0447: //if (CheckForDeadChannel(interp, statePtr)) return -1;
0448:
0449: // Loop over the queued buffers and attempt to flush as
0450: // much as possible of the queued output to the channel.
0451:
0452: while (true) {
0453: // If the queue is empty and there is a ready current buffer, OR if
0454: // the current buffer is full, then move the current buffer to the
0455: // queue.
0456:
0457: if (((curOut != null) && (curOut.nextAdded == curOut.bufLength))
0458: || (bufferReady && (outQueueHead == null))) {
0459: bufferReady = false;
0460: curOut.next = null;
0461: if (outQueueHead == null) {
0462: outQueueHead = curOut;
0463: } else {
0464: outQueueTail.next = curOut;
0465: }
0466: outQueueTail = curOut;
0467: curOut = null;
0468: }
0469: buf = outQueueHead;
0470:
0471: // If we are not being called from an async flush and an async
0472: // flush is active, we just return without producing any output.
0473:
0474: if ((!calledFromAsyncFlush) && bgFlushScheduled) {
0475: return 0;
0476: }
0477:
0478: // If the output queue is still empty, break out of the while loop.
0479:
0480: if (buf == null) {
0481: break; // Out of the "while (true)".
0482: }
0483:
0484: // Produce the output on the channel.
0485:
0486: toWrite = buf.nextAdded - buf.nextRemoved;
0487: //written = (chanPtr->typePtr->outputProc) (chanPtr->instanceData,
0488: // bufPtr->buf + bufPtr->nextRemoved, toWrite,
0489: // &errorCode);
0490: try {
0491: output.write(buf.buf, buf.nextRemoved, toWrite);
0492: written = toWrite;
0493: } catch (IOException ex) {
0494: // FIXME: How can we recover and get posix errors?
0495: ex.printStackTrace(System.err);
0496: errorCode = TclPosixException.EIO; // Generic I/O error ???
0497: written = -1;
0498: }
0499:
0500: // If the write failed completely attempt to start the asynchronous
0501: // flush mechanism and break out of this loop - do not attempt to
0502: // write any more output at this time.
0503:
0504: if (written < 0) {
0505: // If the last attempt to write was interrupted, simply retry.
0506:
0507: if (errorCode == TclPosixException.EINTR) {
0508: errorCode = 0;
0509: continue;
0510: }
0511:
0512: // If the channel is non-blocking and we would have blocked,
0513: // start a background flushing handler and break out of the loop.
0514:
0515: if ((errorCode == TclPosixException.EWOULDBLOCK)
0516: || (errorCode == TclPosixException.EAGAIN)) {
0517: // This used to check for CHANNEL_NONBLOCKING, and panic
0518: // if the channel was blocking. However, it appears
0519: // that setting stdin to -blocking 0 has some effect on
0520: // the stdout when it's a tty channel (dup'ed underneath)
0521:
0522: if (!bgFlushScheduled) {
0523: bgFlushScheduled = true;
0524: updateInterest();
0525: }
0526: errorCode = 0;
0527: break;
0528: }
0529:
0530: // Decide whether to report the error upwards or defer it.
0531:
0532: if (calledFromAsyncFlush) {
0533: if (unreportedError == 0) {
0534: unreportedError = errorCode;
0535: }
0536: } else {
0537: // FIXME: Need to figure out what to do here!
0538: //Tcl_SetErrno(errorCode);
0539: //if (interp != NULL) {
0540: // // Casting away CONST here is safe because the
0541: // // TCL_VOLATILE flag guarantees CONST treatment
0542: // // of the Posix error string.
0543: // Tcl_SetResult(interp,
0544: // (char *) Tcl_PosixError(interp), TCL_VOLATILE);
0545: }
0546:
0547: // When we get an error we throw away all the output
0548: // currently queued.
0549:
0550: discardQueued();
0551: continue;
0552: } else {
0553: wroteSome = true;
0554: }
0555:
0556: buf.nextRemoved += written;
0557:
0558: // If this buffer is now empty, recycle it.
0559:
0560: if (buf.nextRemoved == buf.nextAdded) {
0561: outQueueHead = buf.next;
0562: if (outQueueHead == null) {
0563: outQueueTail = null;
0564: }
0565: recycleBuffer(buf, false);
0566: }
0567: } // Closes "while (1)".
0568:
0569: // If we wrote some data while flushing in the background, we are done.
0570: // We can't finish the background flush until we run out of data and
0571: // the channel becomes writable again. This ensures that all of the
0572: // pending data has been flushed at the system level.
0573:
0574: if (bgFlushScheduled) {
0575: if (wroteSome) {
0576: return errorCode;
0577: } else if (outQueueHead == null) {
0578: bgFlushScheduled = false;
0579: // FIXME: What is this watchProc?
0580: //(chanPtr->typePtr->watchProc)(chanPtr->instanceData,
0581: // statePtr->interestMask);
0582: }
0583: }
0584:
0585: // If the channel is flagged as closed, delete it when the refCount
0586: // drops to zero, the output queue is empty and there is no output
0587: // in the current output buffer.
0588:
0589: if (closed
0590: && (refCount <= 0)
0591: && (outQueueHead == null)
0592: && ((curOut == null) || (curOut.nextAdded == curOut.nextRemoved))) {
0593: return closeChannel(interp, errorCode);
0594: }
0595: return errorCode;
0596:
0597: } finally {
0598: output.flush();
0599:
0600: // In some implementations (Sun JDK 1.4 on Win32)
0601: // the flush method above does not actually sync
0602: // output data or files. Call sync when we know
0603: // the object is a file and not something else
0604: // like a socket stream.
0605:
0606: if (canSync) {
0607: FileOutputStream fos = (FileOutputStream) output;
0608: fos.getFD().sync();
0609: }
0610: }
0611: }
0612:
0613: void setEncoding(String inEncoding) {
0614: encoding = inEncoding;
0615: }
0616:
0617: void setEofChar(char inEofChar) {
0618: eofChar = inEofChar;
0619: }
0620:
0621: void setTranslation(int inTranslation) {
0622: translation = inTranslation;
0623: }
0624:
0625: void setBuffering(int inBuffering) {
0626: buffering = inBuffering;
0627: }
0628:
0629: void setBufferSize(int inBufSize) {
0630: bufSize = inBufSize;
0631: outputStage = null;
0632: }
0633:
0634: void setBlocking(boolean inBlocking) {
0635: blocking = inBlocking;
0636: }
0637:
0638: boolean isBlocked() {
0639: return blocked;
0640: }
0641:
0642: // Helper class to implement integer pass by reference.
0643:
0644: private class IntPtr {
0645: int i;
0646:
0647: IntPtr() {
0648: }
0649:
0650: IntPtr(int value) {
0651: i = value;
0652: }
0653: }
0654:
0655: /**
0656: * RecycleBuffer -> recycleBuffer
0657: *
0658: * Helper function to recycle output buffers. Ensures that
0659: * that curOut is set to a buffer. Only if these conditions
0660: * are met is the buffer released so that it can be
0661: * garbage collected.
0662: */
0663:
0664: private void recycleBuffer(ChannelBuffer buf, boolean mustDiscard) {
0665:
0666: if (mustDiscard)
0667: return;
0668:
0669: // Only save buffers which are at least as big as the requested
0670: // buffersize for the channel. This is to honor dynamic changes
0671: // of the buffersize made by the user.
0672:
0673: if ((buf.bufLength - buf.BUFFER_PADDING) < bufSize) {
0674: return;
0675: }
0676:
0677: if (curOut == null) {
0678: curOut = buf;
0679: buf.nextRemoved = buf.BUFFER_PADDING;
0680: buf.nextAdded = buf.BUFFER_PADDING;
0681: buf.next = null;
0682: }
0683: }
0684:
0685: /**
0686: * DiscardOutputQueued -> discardQueued
0687: *
0688: * Discards all output queued in the output queue of a channel.
0689: */
0690:
0691: private void discardQueued() {
0692: ChannelBuffer buf;
0693:
0694: while (outQueueHead != null) {
0695: buf = outQueueHead;
0696: outQueueHead = buf.next;
0697: recycleBuffer(buf, false);
0698: }
0699: outQueueHead = null;
0700: outQueueTail = null;
0701: }
0702:
0703: /**
0704: * UpdateInterest -> updateInterest
0705: *
0706: * Arrange for the notifier to call us back at appropriate times
0707: * based on the current state of the channel.
0708: */
0709:
0710: void updateInterest() {
0711: // FIXME: Currently unimplemented
0712: }
0713:
0714: /**
0715: * Tcl_OutputBuffered -> getNumBufferedBytes
0716: *
0717: * Return the number of bytes that are current buffered.
0718: */
0719:
0720: int getNumBufferedBytes() {
0721: ChannelBuffer buf;
0722: int IOQueued = 0;
0723: for (buf = outQueueHead; buf != null; buf = buf.next) {
0724: IOQueued += buf.nextAdded - buf.nextRemoved;
0725: }
0726: if ((curOut != null) && (curOut.nextAdded > curOut.nextRemoved)) {
0727: //bufferReady = true;
0728: IOQueued += curOut.nextAdded - curOut.nextRemoved;
0729: }
0730: return IOQueued;
0731: }
0732:
0733: /**
0734: * seekCheckBuferReady
0735: *
0736: * This method is used by the seek command to check
0737: * the channel for buffered output and mark the
0738: * buffer as ready to flush if found.
0739: */
0740:
0741: void seekCheckBuferReady() {
0742: if ((curOut != null) && (curOut.nextAdded > curOut.nextRemoved)) {
0743: bufferReady = true;
0744: }
0745: }
0746:
0747: /**
0748: * TranslateOutputEOL -> translateEOL
0749: *
0750: * Helper function for writeBytes() and writeChars(). Converts the
0751: * '\n' characters in the source buffer into the appropriate EOL
0752: * form specified by the output translation mode.
0753: *
0754: * EOL translation stops either when the source buffer is empty
0755: * or the output buffer is full.
0756: *
0757: * When converting to CRLF mode and there is only 1 byte left in
0758: * the output buffer, this routine stores the '\r' in the last
0759: * byte and then stores the '\n' in the byte just past the end of the
0760: * buffer. The caller is responsible for passing in a buffer that
0761: * is large enough to hold the extra byte.
0762: *
0763: * Results:
0764: *
0765: * The return value is 1 if a '\n' was translated from the source
0766: * buffer, or 0 otherwise -- this can be used by the caller to
0767: * decide to flush a line-based channel even though the channel
0768: * buffer is not full.
0769: *
0770: * dstLenPtr.i is filled with how many bytes of the output buffer
0771: * were used. As mentioned above, this can be one more that
0772: * the output buffer's specified length if a CRLF was stored.
0773: *
0774: * srcLenPtr.i is filled with how many bytes of the source buffer
0775: * were consumed.
0776: *
0777: * It may be obvious, but bears mentioning that when converting
0778: * in CRLF mode (which requires two bytes of storage in the output
0779: * buffer), the number of bytes consumed from the source buffer
0780: * will be less than the number of bytes stored in the output buffer.
0781: *
0782: * @param dstArray, Output buffer to fill with translated bytes or chars.
0783: * @param dstStart, First unused index in the dst output array.
0784: * @param srcArray, Input buffer that holds the bytes or chars to translate
0785: * @param srcStart, Index of first available byte in src array.
0786:
0787: * @param dstLenPtr, On entry, the maximum length of output
0788: * buffer in bytes or chars. On exit, the number of
0789: * bytes or chars actually used in output buffer.
0790: * @param srcLenPtr, On entry, the length of source buffer.
0791: * On exit, the number of bytes or chars read from
0792: * the source buffer.
0793: */
0794:
0795: boolean translateEOL(Object dstArray, int dstStart,
0796: Object srcArray, int srcStart, IntPtr dstLenPtr,
0797: IntPtr srcLenPtr) {
0798: final boolean debug = false;
0799:
0800: // Figure out if the srcArray and dstArray buffers
0801: // are byte or char arrays.
0802: boolean isCharType;
0803: char[] srcArrayChar, dstArrayChar;
0804: byte[] srcArrayByte, dstArrayByte;
0805:
0806: if ((srcArray instanceof char[])
0807: && (dstArray instanceof char[])) {
0808: isCharType = true;
0809: srcArrayChar = (char[]) srcArray;
0810: dstArrayChar = (char[]) dstArray;
0811: srcArrayByte = null;
0812: dstArrayByte = null;
0813: } else if ((srcArray instanceof byte[])
0814: && (dstArray instanceof byte[])) {
0815: isCharType = false;
0816: srcArrayChar = null;
0817: dstArrayChar = null;
0818: srcArrayByte = (byte[]) srcArray;
0819: dstArrayByte = (byte[]) dstArray;
0820: } else {
0821: throw new TclRuntimeError("unknown array argument types");
0822: }
0823:
0824: int src, dst, dstEnd, srcLen;
0825: boolean newlineFound;
0826:
0827: src = srcStart;
0828: dst = dstStart;
0829: newlineFound = false;
0830: srcLen = srcLenPtr.i;
0831:
0832: switch (translation) {
0833: case TclIO.TRANS_LF: {
0834: if (isCharType) {
0835: for (dstEnd = dst + srcLen; dst < dstEnd;) {
0836: if (srcArrayChar[src] == '\n') {
0837: newlineFound = true;
0838: }
0839: dstArrayChar[dst++] = srcArrayChar[src++];
0840: }
0841: } else {
0842: for (dstEnd = dst + srcLen; dst < dstEnd;) {
0843: if (srcArrayByte[src] == '\n') {
0844: newlineFound = true;
0845: }
0846: dstArrayByte[dst++] = srcArrayByte[src++];
0847: }
0848: }
0849: dstLenPtr.i = srcLen;
0850: break;
0851: }
0852: case TclIO.TRANS_CR: {
0853: if (isCharType) {
0854: for (dstEnd = dst + srcLen; dst < dstEnd;) {
0855: if (srcArrayChar[src] == '\n') {
0856: dstArrayChar[dst++] = '\r';
0857: newlineFound = true;
0858: src++;
0859: } else {
0860: dstArrayChar[dst++] = srcArrayChar[src++];
0861: }
0862: }
0863: } else {
0864: for (dstEnd = dst + srcLen; dst < dstEnd;) {
0865: if (srcArrayByte[src] == '\n') {
0866: dstArrayByte[dst++] = (byte) '\r';
0867: newlineFound = true;
0868: src++;
0869: } else {
0870: dstArrayByte[dst++] = srcArrayByte[src++];
0871: }
0872: }
0873: }
0874: dstLenPtr.i = srcLen;
0875: break;
0876: }
0877: case TclIO.TRANS_CRLF: {
0878: // Since this causes the number of bytes to grow, we
0879: // start off trying to put 'srcLen' bytes into the
0880: // output buffer, but allow it to store more bytes, as
0881: // long as there's still source bytes and room in the
0882: // output buffer.
0883:
0884: int dstMax;
0885: //int dstStart, srcStart;
0886:
0887: //dstStart = dst;
0888: dstMax = dst + dstLenPtr.i;
0889:
0890: //srcStart = src;
0891:
0892: if (srcLen < dstLenPtr.i) {
0893: dstEnd = dst + srcLen;
0894: } else {
0895: dstEnd = dst + dstLenPtr.i;
0896: }
0897:
0898: if (isCharType) {
0899: while (dst < dstEnd) {
0900: if (srcArrayChar[src] == '\n') {
0901: if (dstEnd < dstMax) {
0902: dstEnd++;
0903: }
0904: dstArrayChar[dst++] = '\r';
0905: newlineFound = true;
0906: }
0907: dstArrayChar[dst++] = srcArrayChar[src++];
0908: }
0909: } else {
0910: while (dst < dstEnd) {
0911: if (srcArrayByte[src] == '\n') {
0912: if (dstEnd < dstMax) {
0913: dstEnd++;
0914: }
0915: dstArrayByte[dst++] = (byte) '\r';
0916: newlineFound = true;
0917: }
0918: dstArrayByte[dst++] = srcArrayByte[src++];
0919: }
0920: }
0921:
0922: srcLenPtr.i = src - srcStart;
0923: dstLenPtr.i = dst - dstStart;
0924: break;
0925: }
0926: default: {
0927: break;
0928: }
0929: }
0930: return newlineFound;
0931: }
0932:
0933: /**
0934: * Tcl_UtfToExternal -> unicodeToExternal
0935: *
0936: * Convert a source buffer from unicode characters to a specified encoding.
0937: *
0938: * FIXME: Add doc for return values
0939: *
0940: * @param src, Source characters.
0941: * @param srcOff, First index in src input array.
0942: * @param srcLen, Number of characters in src buffer.
0943: * @param dst, Array to store encoded bytes in.
0944: * @param dstOff, First available index in dst array.
0945: * @param dstLen, Length of dst array.
0946: * @param srcReadPtr, Filled with the number of characters from
0947: * the source string that were converted.
0948: * This may be less than the original source
0949: * length if there was a problem converting
0950: * some source characters.
0951: * @param dstWrotePtr, Filled with the number of bytes that were
0952: * stored in the output buffer as a result of
0953: * the conversion
0954: * @param dstCharsPtr, Filled with the number of characters that
0955: * correspond to the bytes stored in the
0956: * output buffer.
0957: */
0958:
0959: int unicodeToExternal(char[] src, int srcOff, int srcLen,
0960: byte[] dst, int dstOff, int dstLen, IntPtr srcReadPtr,
0961: IntPtr dstWrotePtr, IntPtr dstCharsPtr) {
0962: final boolean debug = false;
0963: int result;
0964:
0965: if (encoding == null) {
0966: throw new TclRuntimeError(
0967: "unicodeToExternal called with null encoding");
0968: }
0969:
0970: if (debug) {
0971: System.out.println("unicodeToExternal(" + srcLen + " "
0972: + dstLen + ")");
0973: }
0974:
0975: // If encoder was flushed already then return 0.
0976:
0977: if ((srcLen == 0) && !encodingEnd) {
0978: srcReadPtr.i = 0;
0979: if (dstWrotePtr != null)
0980: dstWrotePtr.i = 0;
0981: if (dstCharsPtr != null)
0982: dstCharsPtr.i = 0;
0983: return 0;
0984: }
0985:
0986: if (debug) {
0987: System.out.println("now to encode char array of length "
0988: + srcLen);
0989: System.out.println("srcOff is " + srcOff);
0990: for (int i = srcOff; i < (srcOff + srcLen); i++) {
0991: System.out.println("(char) '" + src[i] + "'");
0992: }
0993: System.out.println("encoded as " + encoding);
0994: }
0995:
0996: if (cse == null) {
0997: // Note that UnsupportedCharsetException should never be raised
0998: // here since EncodingCmd.isSupported() should have already
0999: // returned true for this encoding.
1000:
1001: Charset chrset = Charset.forName(encoding);
1002: cse = chrset.newEncoder();
1003: }
1004:
1005: int chars_read, bytes_written;
1006: int bytes_flushed = 0;
1007:
1008: // A CharBuffer wraps the src char[] and
1009: // handles buffer size issues. A ByteBuffer
1010: // wraps the dst char[] and handles buffer
1011: // size issues.
1012:
1013: CharBuffer srcb = CharBuffer.wrap(src, srcOff, srcLen);
1014: ByteBuffer dstb = ByteBuffer.wrap(dst, dstOff, dstLen);
1015:
1016: int srcbStartPos = srcb.position();
1017: int dstbStartPos = dstb.position();
1018:
1019: // Pass atEOF flag as true when encodingEnd flag
1020: // was set to true in close().
1021:
1022: boolean atEOF = encodingEnd;
1023:
1024: CoderResult cresult = cse.encode(srcb, dstb, atEOF);
1025:
1026: chars_read = srcb.position() - srcbStartPos;
1027: bytes_written = dstb.position() - dstbStartPos;
1028:
1029: if (debug) {
1030: System.out.println("encoded " + bytes_written
1031: + " bytes from " + chars_read
1032: + " chars (EOF flag was " + atEOF + ")");
1033: }
1034:
1035: // For the case where an encoder needs to write bytes
1036: // at the end of the data, the close() method passes
1037: // an empty char[] and sets encodingEnd.
1038:
1039: if (atEOF) {
1040: if (chars_read != 0 && bytes_written != 0) {
1041: throw new TclRuntimeError(
1042: "Should have encoded no chars at EOF, "
1043: + chars_read + " " + bytes_written);
1044: }
1045:
1046: cresult = cse.flush(dstb);
1047:
1048: bytes_flushed = dstb.position() - dstbStartPos;
1049: bytes_written += bytes_flushed;
1050:
1051: if (debug) {
1052: System.out.println("flushed " + bytes_flushed
1053: + " bytes at EOF");
1054: }
1055:
1056: encodingEnd = false;
1057: }
1058:
1059: if (!atEOF && (chars_read == 0) && (bytes_written == 0)) {
1060: throw new TclRuntimeError("No characters converted");
1061: }
1062:
1063: srcReadPtr.i = chars_read;
1064: if (dstWrotePtr != null)
1065: dstWrotePtr.i = bytes_written;
1066: if (dstCharsPtr != null)
1067: dstCharsPtr.i = chars_read;
1068:
1069: // FIXME: When do we return error codes?
1070: result = 0;
1071:
1072: return result;
1073: }
1074:
1075: /**
1076: * WriteBytes -> writeBytes
1077: *
1078: * Write a sequence of bytes into an output buffer, may queue the
1079: * buffer for output if it gets full, and also remembers whether the
1080: * current buffer is ready e.g. if it contains a newline and we are in
1081: * line buffering mode.
1082: *
1083: * The number of bytes written or -1 in case of error. If -1,
1084: * Tcl_GetErrno will return the error code.
1085: *
1086: * May buffer up output and may cause output to be produced on the
1087: * channel.
1088: *
1089: * @param src Bytes to write.
1090: * @param srfOff First index in src array.
1091: * @param srfLen Number of bytes to write.
1092: */
1093:
1094: int writeBytes(byte[] srcArray, int srcOff, int srcLen)
1095: throws IOException {
1096: ChannelBuffer buf;
1097: byte[] dstArray;
1098: int dst, src, dstMax, sawLF, total, savedLF;
1099: IntPtr dstLen = new IntPtr(), toWrite = new IntPtr();
1100:
1101: total = 0;
1102: sawLF = 0;
1103: savedLF = 0;
1104: src = srcOff;
1105:
1106: // Loop over all bytes in src, storing them in output buffer with
1107: // proper EOL translation.
1108:
1109: while (srcLen + savedLF > 0) {
1110: buf = curOut;
1111: if (buf == null) {
1112: buf = new ChannelBuffer(bufSize);
1113: curOut = buf;
1114: }
1115: //dst = bufPtr->buf + bufPtr->nextAdded;
1116: dstArray = buf.buf;
1117: dst = buf.nextAdded;
1118: dstMax = buf.bufLength - buf.nextAdded;
1119: dstLen.i = dstMax;
1120:
1121: toWrite.i = dstLen.i;
1122: if (toWrite.i > srcLen) {
1123: toWrite.i = srcLen;
1124: }
1125:
1126: if (savedLF != 0) {
1127: // A '\n' was left over from last call to translateEOL()
1128: // and we need to store it in this buffer. If the channel is
1129: // line-based, we will need to flush it.
1130:
1131: dstArray[dst++] = (byte) '\n';
1132: dstLen.i--;
1133: sawLF++;
1134: }
1135: if (translateEOL(dstArray, dst, srcArray, src, dstLen,
1136: toWrite)) {
1137: sawLF++;
1138: }
1139: dstLen.i += savedLF;
1140: savedLF = 0;
1141:
1142: if (dstLen.i > dstMax) {
1143: savedLF = 1;
1144: dstLen.i = dstMax;
1145: }
1146: buf.nextAdded += dstLen.i;
1147: if (checkFlush(buf, (sawLF != 0)) != 0) {
1148: return -1;
1149: }
1150: total += dstLen.i;
1151: src += toWrite.i;
1152: srcLen -= toWrite.i;
1153: sawLF = 0;
1154: }
1155: return total;
1156: }
1157:
1158: /**
1159: * CheckFlush -> checkFlush
1160: *
1161: * Helper function for writeBytes() and writeChars(). If the
1162: * channel buffer is ready to be flushed, flush it.
1163: *
1164: * The return value is -1 if there was a problem flushing the
1165: * channel buffer, or 0 otherwise.
1166: *
1167: * The buffer will be recycled if it is flushed.
1168: *
1169: * @param buf Channel buffer to possibly flush.
1170: * @param newlineFlag True if a the channel buffer
1171: * contains a newline.
1172: */
1173:
1174: int checkFlush(ChannelBuffer buf, boolean newlineFlag)
1175: throws IOException {
1176: // The current buffer is ready for output:
1177: // 1. if it is full.
1178: // 2. if it contains a newline and this channel is line-buffered.
1179: // 3. if it contains any output and this channel is unbuffered.
1180:
1181: if (!bufferReady) {
1182: if (buf.nextAdded == buf.bufLength) {
1183: bufferReady = true;
1184: } else if (buffering == TclIO.BUFF_LINE) {
1185: if (newlineFlag) {
1186: bufferReady = true;
1187: }
1188: } else if (buffering == TclIO.BUFF_NONE) {
1189: bufferReady = true;
1190: }
1191: }
1192: if (bufferReady) {
1193: if (flushChannel(null, false) != 0) {
1194: return -1;
1195: }
1196: }
1197: return 0;
1198: }
1199:
1200: /**
1201: * WriteChars -> writeChars
1202: *
1203: * Convert chars to the channel's external encoding and
1204: * write the produced bytes into an output buffer, may queue the
1205: * buffer for output if it gets full, and also remembers whether the
1206: * current buffer is ready e.g. if it contains a newline and we are in
1207: * line buffering mode.
1208: *
1209: * The number of bytes written or -1 in case of error. If -1,
1210: * Tcl_GetErrno will return the error code.
1211: *
1212: * May buffer up output and may cause output to be produced on the
1213: * channel.
1214: *
1215: * @param src Chars to write.
1216: * @param srfOff First index in src array.
1217: * @param srfLen Number of chars to write.
1218: */
1219:
1220: int writeChars(char[] srcArray, int srcOff, int srcLen)
1221: throws IOException {
1222: final boolean debug = false;
1223: if (debug) {
1224: System.out.println("writeChars(" + srcLen + ")");
1225: }
1226:
1227: //ChannelState *statePtr = chanPtr->state; // state info for channel
1228: ChannelBuffer buf;
1229: char[] stageArray;
1230: byte[] dstArray;
1231: int stage, src, dst;
1232: int saved, savedLF, sawLF, total, dstLen, stageMax;
1233: int endEncoding, result;
1234: boolean consumedSomething;
1235: //Tcl_Encoding encoding;
1236: byte[] safe = new byte[ChannelBuffer.BUFFER_PADDING];
1237: IntPtr stageLen = new IntPtr(), toWrite = new IntPtr();
1238: IntPtr stageRead = new IntPtr(), dstWrote = new IntPtr();
1239:
1240: total = 0;
1241: sawLF = 0;
1242: savedLF = 0;
1243: saved = 0;
1244: //encoding = statePtr->encoding;
1245: src = 0;
1246:
1247: // Write the terminated escape sequence even if srcLen is 0.
1248:
1249: endEncoding = (encodingEnd ? 1 : 0);
1250:
1251: // Loop over all characters in src, storing them in staging buffer
1252: // with proper EOL translation.
1253:
1254: consumedSomething = true;
1255: while (consumedSomething
1256: && (srcLen + savedLF + endEncoding > 0)) {
1257: consumedSomething = false;
1258: if (outputStage == null) {
1259: outputStage = new char[bufSize + 2];
1260: }
1261: stageArray = outputStage;
1262: stage = 0;
1263: stageMax = bufSize;
1264: stageLen.i = stageMax;
1265:
1266: toWrite.i = stageLen.i;
1267: if (toWrite.i > srcLen) {
1268: toWrite.i = srcLen;
1269: }
1270:
1271: if (savedLF != 0) {
1272: // A '\n' was left over from last call to TranslateOutputEOL()
1273: // and we need to store it in the staging buffer. If the
1274: // channel is line-based, we will need to flush the output
1275: // buffer (after translating the staging buffer).
1276:
1277: stageArray[stage++] = '\n';
1278: stageLen.i--;
1279: sawLF++;
1280: }
1281: if (translateEOL(stageArray, stage, srcArray, src,
1282: stageLen, toWrite)) {
1283: sawLF++;
1284: }
1285:
1286: stage -= savedLF;
1287: stageLen.i += savedLF;
1288: savedLF = 0;
1289:
1290: if (stageLen.i > stageMax) {
1291: savedLF = 1;
1292: stageLen.i = stageMax;
1293: }
1294: src += toWrite.i;
1295: srcLen -= toWrite.i;
1296:
1297: if (debug) {
1298: System.out.println("moved " + stageLen.i
1299: + " chars to stageArray");
1300: }
1301:
1302: // Loop over all characters in staging buffer, converting them
1303: // to external encoding, storing them in output buffer.
1304:
1305: while (stageLen.i + saved + endEncoding > 0) {
1306: buf = curOut;
1307: if (buf == null) {
1308: buf = new ChannelBuffer(bufSize);
1309: curOut = buf;
1310: }
1311: // dst = buf.buf + buf.nextAdded;
1312: dstArray = buf.buf;
1313: dst = buf.nextAdded;
1314: dstLen = buf.bufLength - buf.nextAdded;
1315:
1316: if (saved != 0) {
1317: // Here's some translated bytes left over from the last
1318: // buffer that we need to stick at the beginning of this
1319: // buffer.
1320:
1321: System.arraycopy(safe, 0, dstArray, dst, saved);
1322: buf.nextAdded += saved;
1323: dst += saved;
1324: dstLen -= saved;
1325: saved = 0;
1326: }
1327:
1328: if (debug) {
1329: System.out.println("invoking unicodeToExternal("
1330: + stageLen.i + ")");
1331: }
1332:
1333: result = unicodeToExternal(stageArray, stage,
1334: stageLen.i, dstArray, dst, dstLen
1335: + ChannelBuffer.BUFFER_PADDING,
1336: stageRead, dstWrote, null);
1337:
1338: // Fix for SF #506297, reported by Martin Forssen
1339: // <ruric@users.sourceforge.net>.
1340: //
1341: // The encoding chosen in the script exposing the bug writes out
1342: // three intro characters when TCL_ENCODING_START is set, but does
1343: // not consume any input as TCL_ENCODING_END is cleared. As some
1344: // output was generated the enclosing loop calls UtfToExternal
1345: // again, again with START set. Three more characters in the out
1346: // and still no use of input ... To break this infinite loop we
1347: // remove TCL_ENCODING_START from the set of flags after the first
1348: // call (no condition is required, the later calls remove an unset
1349: // flag, which is a no-op). This causes the subsequent calls to
1350: // UtfToExternal to consume and convert the actual input.
1351:
1352: encodingStart = false;
1353:
1354: // The following can never happen since we use unicode characters.
1355: //
1356: //if ((result != 0) && ((stageRead.i + dstWrote.i) == 0)) {
1357: // // We have an incomplete UTF-8 character at the end of the
1358: // // staging buffer. It will get moved to the beginning of the
1359: // // staging buffer followed by more bytes from src.
1360: //
1361: // src -= stageLen.i;
1362: // srcLen += stageLen.i;
1363: // stageLen.i = 0;
1364: // savedLF = 0;
1365: // break;
1366: //}
1367: buf.nextAdded += dstWrote.i;
1368: if (buf.nextAdded > buf.bufLength) {
1369: // When translating from unicode to external encoding, we
1370: // allowed the translation to produce a character that
1371: // crossed the end of the output buffer, so that we would
1372: // get a completely full buffer before flushing it. The
1373: // extra bytes will be moved to the beginning of the next
1374: // buffer.
1375:
1376: saved = buf.nextAdded - buf.bufLength;
1377: System.arraycopy(dstArray, dst + dstLen, safe, 0,
1378: saved);
1379: buf.nextAdded = buf.bufLength;
1380: }
1381: if (checkFlush(buf, (sawLF != 0)) != 0) {
1382: return -1;
1383: }
1384:
1385: total += dstWrote.i;
1386: stage += stageRead.i;
1387: stageLen.i -= stageRead.i;
1388: sawLF = 0;
1389:
1390: consumedSomething = true;
1391:
1392: // If all translated characters are written to the buffer,
1393: // endEncoding is set to 0 because the escape sequence may be
1394: // output.
1395:
1396: if ((stageLen.i + saved == 0) && (result == 0)) {
1397: endEncoding = 0;
1398: }
1399: }
1400: }
1401:
1402: // If nothing was written and it happened because there was no progress
1403: // in the UTF conversion, we throw an error.
1404:
1405: if (!consumedSomething && (total == 0)) {
1406: //Tcl_SetErrno (EINVAL);
1407: return -1;
1408: }
1409: return total;
1410: }
1411:
1412: /**
1413: * DoWriteChars -> doWriteChars
1414: *
1415: * Takes a sequence of characters and converts them for output
1416: * using the channel's current encoding, may queue the buffer for
1417: * output if it gets full, and also remembers whether the current
1418: * buffer is ready e.g. if it contains a newline and we are in
1419: * line buffering mode. Compensates stacking, i.e. will redirect the
1420: * data from the specified channel to the topmost channel in a stack.
1421: *
1422: * The number of bytes written or -1 in case of error. If -1,
1423: * Tcl_GetErrno will return the error code.
1424: *
1425: * May buffer up output and may cause output to be produced on the
1426: * channel.
1427: *
1428: * @param src Chars to write.
1429: * @param srfOff First index in src array.
1430: * @param srfLen Number of chars to write.
1431: */
1432:
1433: int doWriteChars(char[] src, int srcOff, int srcLen) {
1434: return -1;
1435: }
1436:
1437: /**
1438: * Tcl_WriteObj -> writeObj
1439: *
1440: * Takes the Tcl object and queues its contents for output. If the
1441: * encoding of the channel is NULL, takes the byte-array representation
1442: * of the object and queues those bytes for output. Otherwise, takes
1443: * the characters in the UTF-8 (string) representation of the object
1444: * and converts them for output using the channel's current encoding.
1445: * May flush internal buffers to output if one becomes full or is ready
1446: * for some other reason, e.g. if it contains a newline and the channel
1447: * is in line buffering mode.
1448: *
1449: * The number of bytes written or -1 in case of error. If -1,
1450: * Tcl_GetErrno will return the error code.
1451: *
1452: * May buffer up output and may cause output to be produced on the
1453: * channel.
1454: *
1455: * @param obj The object to write.
1456: */
1457:
1458: int writeObj(TclObject obj) throws IOException {
1459: // Always use the topmost channel of the stack
1460:
1461: //char *src;
1462: int srcLen;
1463:
1464: //statePtr = ((Channel *) chan)->state;
1465: //chanPtr = statePtr->topChanPtr;
1466:
1467: //if (CheckChannelErrors(statePtr, TCL_WRITABLE) != 0) {
1468: // return -1;
1469: //}
1470:
1471: if (encoding == null) {
1472: srcLen = TclByteArray.getLength(null, obj);
1473: byte[] bytes = TclByteArray.getBytes(null, obj);
1474: return writeBytes(bytes, 0, srcLen);
1475: } else {
1476: String data = obj.toString();
1477: final int num_chars = data.length();
1478: // FIXME: Use outputStage here?
1479: char[] chars = new char[num_chars];
1480: data.getChars(0, num_chars, chars, 0);
1481: return writeChars(chars, 0, num_chars);
1482: }
1483: }
1484:
1485: }
|