001: /*
002: * Channel.java
003: *
004: * Copyright (c) 1997 Sun Microsystems, Inc.
005: *
006: * See the file "license.terms" for information on usage and
007: * redistribution of this file, and for a DISCLAIMER OF ALL
008: * WARRANTIES.
009: *
010: * RCS: @(#) $Id: Channel.java,v 1.27 2006/07/07 23:36:00 mdejong Exp $
011: */
012:
013: package tcl.lang;
014:
015: import java.io.*;
016: import java.util.Hashtable;
017:
018: /**
019: * The Channel class provides functionality that will
020: * be needed for any type of Tcl channel. It performs
021: * generic reads, writes, without specifying how a
022: * given channel is actually created. Each new channel
023: * type will need to extend the abstract Channel class
024: * and override any methods it needs to provide a
025: * specific implementation for.
026: */
027:
028: abstract class Channel {
029:
030: /**
031: * The read, write, append and create flags are set here. The
032: * variables used to set the flags are found in the class TclIO.
033: */
034:
035: protected int mode;
036:
037: /**
038: * This is a unique name that sub-classes need to set. It is used
039: * as the key in the hashtable of registered channels (in interp).
040: */
041:
042: private String chanName;
043:
044: /**
045: * How many interpreters hold references to this IO channel?
046: */
047:
048: protected int refCount = 0;
049:
050: /**
051: * Tcl input and output objecs. These are like a mix between
052: * a Java Stream and a Reader.
053: */
054:
055: protected TclInputStream input = null;
056: protected TclOutputStream output = null;
057:
058: /**
059: * Set to false when channel is in non-blocking mode.
060: */
061:
062: protected boolean blocking = true;
063:
064: /**
065: * Buffering (full,line, or none)
066: */
067:
068: protected int buffering = TclIO.BUFF_FULL;
069:
070: /**
071: * Buffer size, in bytes, allocated for channel to store input or output
072: */
073:
074: protected int bufferSize = 4096;
075:
076: /**
077: * Name of Java encoding for this Channel.
078: * A null value means use no encoding (binary).
079: */
080:
081: // FIXME: Check to see if this field is updated after a call
082: // to "encoding system $enc" for new Channel objects!
083: protected String encoding;
084: protected int bytesPerChar;
085:
086: /**
087: * Translation mode for end-of-line character
088: */
089:
090: protected int inputTranslation = TclIO.TRANS_AUTO;
091: protected int outputTranslation = TclIO.TRANS_PLATFORM;
092:
093: /**
094: * If nonzero, use this as a signal of EOF on input.
095: */
096:
097: protected char inputEofChar = 0;
098:
099: /**
100: * If nonzero, append this to a writeable channel on close.
101: */
102:
103: protected char outputEofChar = 0;
104:
105: Channel() {
106: setEncoding(EncodingCmd.systemJavaEncoding);
107: }
108:
109: /**
110: * Tcl_ReadChars -> read
111: *
112: * Read data from the Channel into the given TclObject.
113: *
114: * @param interp is used for TclExceptions.
115: * @param tobj the object data will be added to.
116: * @param readType specifies if the read should read the entire
117: * buffer (TclIO.READ_ALL), the next line
118: * (TclIO.READ_LINE), of a specified number
119: * of bytes (TclIO.READ_N_BYTES).
120: * @param numBytes the number of bytes/chars to read. Used only
121: * when the readType is TclIO.READ_N_BYTES.
122: * @return the number of bytes read.
123: * Returns -1 on EOF or on error.
124: * @exception TclException is thrown if read occurs on WRONLY channel.
125: * @exception IOException is thrown when an IO error occurs that was not
126: * correctly tested for. Most cases should be caught.
127: */
128:
129: int read(Interp interp, TclObject tobj, int readType, int numBytes)
130: throws IOException, TclException {
131: TclObject dataObj;
132:
133: checkRead(interp);
134: initInput();
135:
136: switch (readType) {
137: case TclIO.READ_ALL: {
138: return input.doReadChars(tobj, -1);
139: }
140: case TclIO.READ_LINE: {
141: return input.getsObj(tobj);
142: }
143: case TclIO.READ_N_BYTES: {
144: return input.doReadChars(tobj, numBytes);
145: }
146: default: {
147: throw new TclRuntimeError(
148: "Channel.read: Invalid read mode.");
149: }
150: }
151: }
152:
153: /**
154: * Tcl_WriteObj -> write
155: *
156: * Write data to the Channel
157: *
158: * @param interp is used for TclExceptions.
159: * @param outData the TclObject that holds the data to write.
160: */
161:
162: void write(Interp interp, TclObject outData) throws IOException,
163: TclException {
164:
165: checkWrite(interp);
166: initOutput();
167:
168: // FIXME: Is it possible for a write to happen with a null output?
169: if (output != null) {
170: output.writeObj(outData);
171: }
172: }
173:
174: /**
175: * Tcl_WriteChars -> write
176: *
177: * Write string data to the Channel.
178: *
179: * @param interp is used for TclExceptions.
180: * @param outStr the String object to write.
181: */
182:
183: void write(Interp interp, String outStr) throws IOException,
184: TclException {
185: write(interp, TclString.newInstance(outStr));
186: }
187:
188: /**
189: * Close the Channel. The channel is only closed, it is
190: * the responsibility of the "closer" to remove the channel from
191: * the channel table.
192: */
193:
194: void close() throws IOException {
195:
196: IOException ex = null;
197:
198: if (input != null) {
199: try {
200: input.close();
201: } catch (IOException e) {
202: ex = e;
203: }
204: input = null;
205: }
206:
207: if (output != null) {
208: try {
209: output.close();
210: } catch (IOException e) {
211: ex = e;
212: }
213: output = null;
214: }
215:
216: if (ex != null)
217: throw ex;
218: }
219:
220: /**
221: * Flush the Channel.
222: *
223: * @exception TclException is thrown when attempting to flush a
224: * read only channel.
225: * @exception IOEcception is thrown for all other flush errors.
226: */
227:
228: void flush(Interp interp) throws IOException, TclException {
229:
230: checkWrite(interp);
231:
232: if (output != null) {
233: output.flush();
234: }
235: }
236:
237: /**
238: * Move the current file pointer. If seek is not supported on the
239: * given channel then -1 will be returned. A subclass should
240: * override this method if it supports the seek operation.
241: *
242: * @param interp currrent interpreter.
243: * @param offset The number of bytes to move the file pointer.
244: * @param mode where to begin incrementing the file pointer; beginning,
245: * current, end.
246: */
247:
248: void seek(Interp interp, long offset, int mode) throws IOException,
249: TclException {
250: throw new TclPosixException(interp, TclPosixException.EINVAL,
251: true, "error during seek on \"" + getChanName() + "\"");
252: }
253:
254: /**
255: * Return the current file pointer. If tell is not supported on the
256: * given channel then -1 will be returned. A subclass should override
257: * this method if it supports the tell operation.
258: */
259:
260: long tell() throws IOException {
261: return (long) -1;
262: }
263:
264: /**
265: * Setup the TclInputStream on the first call to read
266: */
267:
268: protected void initInput() throws IOException {
269: if (input != null)
270: return;
271:
272: input = new TclInputStream(getInputStream());
273: input.setEncoding(encoding);
274: input.setTranslation(inputTranslation);
275: input.setEofChar(inputEofChar);
276: input.setBuffering(buffering);
277: input.setBufferSize(bufferSize);
278: input.setBlocking(blocking);
279: }
280:
281: /**
282: * Setup the TclOutputStream on the first call to write
283: */
284:
285: protected void initOutput() throws IOException {
286: if (output != null)
287: return;
288:
289: output = new TclOutputStream(getOutputStream());
290: output.setEncoding(encoding);
291: output.setTranslation(outputTranslation);
292: output.setEofChar(outputEofChar);
293: output.setBuffering(buffering);
294: output.setBufferSize(bufferSize);
295: output.setBlocking(blocking);
296: if (getChanType().equals("file")) {
297: output.setSync(true);
298: }
299: }
300:
301: /**
302: * Returns true if the last read reached the EOF.
303: */
304:
305: final boolean eof() {
306: if (input != null)
307: return input.eof();
308: else
309: return false;
310: }
311:
312: /**
313: * This method should be overridden in the subclass to provide
314: * a channel specific InputStream object.
315: */
316:
317: protected abstract InputStream getInputStream() throws IOException;
318:
319: /**
320: * This method should be overridden in the subclass to provide
321: * a channel specific OutputStream object.
322: */
323:
324: protected abstract OutputStream getOutputStream()
325: throws IOException;
326:
327: /**
328: * Gets the chanName that is the key for the chanTable hashtable.
329: * @return channelId
330: */
331:
332: String getChanName() {
333: return chanName;
334: }
335:
336: /**
337: * Return a string that describes the channel type.
338: *
339: * This is the equivilent of the Tcl_ChannelType->typeName field.
340: */
341:
342: abstract String getChanType();
343:
344: /**
345: * Return number of references to this Channel.
346: */
347:
348: int getRefCount() {
349: return refCount;
350: }
351:
352: /**
353: * Sets the chanName that is the key for the chanTable hashtable.
354: * @param chan the unique channelId
355: */
356:
357: void setChanName(String chan) {
358: chanName = chan;
359: }
360:
361: boolean isReadOnly() {
362: return ((mode & TclIO.RDONLY) != 0);
363: }
364:
365: boolean isWriteOnly() {
366: return ((mode & TclIO.WRONLY) != 0);
367: }
368:
369: boolean isReadWrite() {
370: return ((mode & TclIO.RDWR) != 0);
371: }
372:
373: // Helper methods to check read/write permission and raise a
374: // TclException if reading is not allowed.
375:
376: protected void checkRead(Interp interp) throws TclException {
377: if (!isReadOnly() && !isReadWrite()) {
378: throw new TclException(interp, "channel \"" + getChanName()
379: + "\" wasn't opened for reading");
380: }
381: }
382:
383: protected void checkWrite(Interp interp) throws TclException {
384: if (!isWriteOnly() && !isReadWrite()) {
385: throw new TclException(interp, "channel \"" + getChanName()
386: + "\" wasn't opened for writing");
387: }
388: }
389:
390: /**
391: * Query blocking mode.
392: */
393:
394: boolean getBlocking() {
395: return blocking;
396: }
397:
398: /**
399: * Set blocking mode.
400: *
401: * @param blocking new blocking mode
402: */
403:
404: void setBlocking(boolean inBlocking) {
405: blocking = inBlocking;
406:
407: if (input != null)
408: input.setBlocking(blocking);
409: if (output != null)
410: output.setBlocking(blocking);
411: }
412:
413: /**
414: * Query buffering mode.
415: */
416:
417: int getBuffering() {
418: return buffering;
419: }
420:
421: /**
422: * Set buffering mode
423: *
424: * @param buffering One of TclIO.BUFF_FULL, TclIO.BUFF_LINE,
425: * or TclIO.BUFF_NONE
426: */
427:
428: void setBuffering(int inBuffering) {
429: if (inBuffering < TclIO.BUFF_FULL
430: || inBuffering > TclIO.BUFF_NONE)
431: throw new TclRuntimeError(
432: "invalid buffering mode in Channel.setBuffering()");
433:
434: buffering = inBuffering;
435: if (input != null)
436: input.setBuffering(buffering);
437: if (output != null)
438: output.setBuffering(buffering);
439: }
440:
441: /**
442: * Query buffer size
443: */
444:
445: int getBufferSize() {
446: return bufferSize;
447: }
448:
449: /**
450: * Tcl_SetChannelBufferSize -> setBufferSize
451: *
452: * @param size new buffer size
453: */
454:
455: void setBufferSize(int size) {
456:
457: // If the buffer size is smaller than 10 bytes or larger than 1 Meg
458: // do not accept the requested size and leave the current buffer size.
459:
460: if ((size < 10) || (size > (1024 * 1024))) {
461: return;
462: }
463:
464: bufferSize = size;
465: if (input != null)
466: input.setBufferSize(bufferSize);
467: if (output != null)
468: output.setBufferSize(bufferSize);
469: }
470:
471: int getNumBufferedInputBytes() {
472: if (input != null)
473: return input.getNumBufferedBytes();
474: else
475: return 0;
476: }
477:
478: int getNumBufferedOutputBytes() {
479: if (output != null)
480: return output.getNumBufferedBytes();
481: else
482: return 0;
483: }
484:
485: /**
486: * Tcl_InputBlocked -> isBlocked
487: *
488: * Returns true if input is blocked on this channel, false otherwise.
489: *
490: */
491:
492: boolean isBlocked(Interp interp) throws TclException {
493: checkRead(interp);
494:
495: if (input != null)
496: return input.isBlocked();
497: else
498: return false;
499: }
500:
501: /**
502: * Returns true if a background flush is waiting to happen.
503: */
504:
505: boolean isBgFlushScheduled() {
506: // FIXME: Need to query output here
507: return false;
508: }
509:
510: /**
511: * Channel is in CRLF eol input translation mode and the last
512: * byte seen was a CR.
513: */
514:
515: boolean inputSawCR() {
516: if (input != null)
517: return input.sawCR();
518: return false;
519: }
520:
521: /**
522: * Query encoding
523: *
524: * @return Name of Channel's Java encoding (null if no encoding)
525: */
526:
527: String getEncoding() {
528: return encoding;
529: }
530:
531: /**
532: * Set new Java encoding
533: */
534:
535: void setEncoding(String inEncoding) {
536: encoding = inEncoding;
537: if (encoding == null) {
538: bytesPerChar = 1;
539: } else {
540: bytesPerChar = EncodingCmd.getBytesPerChar(encoding);
541: }
542:
543: if (input != null)
544: input.setEncoding(encoding);
545: if (output != null)
546: output.setEncoding(encoding);
547:
548: // FIXME: Pass bytesPerChar to input and output
549: }
550:
551: /**
552: * Query input translation
553: */
554:
555: int getInputTranslation() {
556: return inputTranslation;
557: }
558:
559: /**
560: * Set new input translation
561: */
562:
563: void setInputTranslation(int translation) {
564: inputTranslation = translation;
565: if (input != null)
566: input.setTranslation(inputTranslation);
567: }
568:
569: /**
570: * Query output translation
571: */
572:
573: int getOutputTranslation() {
574: return outputTranslation;
575: }
576:
577: /**
578: * Set new output translation
579: */
580:
581: void setOutputTranslation(int translation) {
582: outputTranslation = translation;
583: if (output != null)
584: output.setTranslation(outputTranslation);
585: }
586:
587: /**
588: * Query input eof character
589: */
590:
591: char getInputEofChar() {
592: return inputEofChar;
593: }
594:
595: /**
596: * Set new input eof character
597: */
598:
599: void setInputEofChar(char inEof) {
600: // Store as a byte, not a unicode character
601: inputEofChar = (char) (inEof & 0xFF);
602: if (input != null)
603: input.setEofChar(inputEofChar);
604: }
605:
606: /**
607: * Query output eof character
608: */
609:
610: char getOutputEofChar() {
611: return outputEofChar;
612: }
613:
614: /**
615: * Set new output eof character
616: */
617:
618: void setOutputEofChar(char outEof) {
619: // Store as a byte, not a unicode character
620: outputEofChar = (char) (outEof & 0xFF);
621: if (output != null)
622: output.setEofChar(outputEofChar);
623: }
624:
625: }
|