2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.io
.OutputStream
;
32 import java
.io
.IOException
;
34 import static jexer
.net
.TelnetSocket
.*;
37 * TelnetOutputStream works with TelnetSocket to perform the telnet protocol.
39 public class TelnetOutputStream
extends OutputStream
{
41 // ------------------------------------------------------------------------
42 // Variables --------------------------------------------------------------
43 // ------------------------------------------------------------------------
46 * The root TelnetSocket that has my telnet protocol state.
48 private TelnetSocket master
;
51 * The raw socket's OutputStream.
53 private OutputStream output
;
56 * When true, the last byte the caller passed to write() was a CR.
58 private boolean writeCR
= false;
60 // ------------------------------------------------------------------------
61 // Constructors -----------------------------------------------------------
62 // ------------------------------------------------------------------------
65 * Package private constructor.
67 * @param master the master TelnetSocket
68 * @param output the underlying socket's OutputStream
70 TelnetOutputStream(final TelnetSocket master
, final OutputStream output
) {
75 // ------------------------------------------------------------------------
76 // OutputStrem ------------------------------------------------------------
77 // ------------------------------------------------------------------------
80 * Closes this output stream and releases any system resources associated
83 * @throws IOException if an I/O error occurs
86 public void close() throws IOException
{
94 * Flushes this output stream and forces any buffered output bytes to be
97 * @throws IOException if an I/O error occurs
100 public void flush() throws IOException
{
101 if ((master
.binaryMode
== false) && (writeCR
== true)) {
102 // The last byte sent to this.write() was a CR, which was never
103 // actually sent. So send the CR in ascii mode, then flush.
104 // CR <anything> -> CR NULL
113 * Writes b.length bytes from the specified byte array to this output
117 * @throws IOException if an I/O error occurs
120 public void write(final byte[] b
) throws IOException
{
121 writeImpl(b
, 0, b
.length
);
125 * Writes len bytes from the specified byte array starting at offset off
126 * to this output stream.
129 * @param off the start offset in the data.
130 * @param len the number of bytes to write.
131 * @throws IOException if an I/O error occurs
134 public void write(final byte[] b
, final int off
,
135 final int len
) throws IOException
{
137 writeImpl(b
, off
, len
);
141 * Writes the specified byte to this output stream.
143 * @param b the byte to write.
144 * @throws IOException if an I/O error occurs
147 public void write(final int b
) throws IOException
{
148 byte [] bytes
= new byte[1];
150 writeImpl(bytes
, 0, 1);
153 // ------------------------------------------------------------------------
154 // TelnetOutputStrem ------------------------------------------------------
155 // ------------------------------------------------------------------------
158 * Writes b.length bytes from the specified byte array to this output
159 * stream. Note package private access.
162 * @throws IOException if an I/O error occurs
164 void rawWrite(final byte[] b
) throws IOException
{
165 output
.write(b
, 0, b
.length
);
169 * Writes len bytes from the specified byte array starting at offset off
170 * to this output stream.
173 * @param off the start offset in the data.
174 * @param len the number of bytes to write.
175 * @throws IOException if an I/O error occurs
177 private void writeImpl(final byte[] b
, final int off
,
178 final int len
) throws IOException
{
180 byte [] writeBuffer
= new byte[Math
.max(len
, 4)];
181 int writeBufferI
= 0;
183 for (int i
= 0; i
< len
; i
++) {
184 if (writeBufferI
>= writeBuffer
.length
- 4) {
185 // Flush what we have generated so far and reset the buffer,
186 // because the next byte could generate up to 4 output bytes
187 // (CR <something> <IAC> <IAC>).
188 output
.write(writeBuffer
, 0, writeBufferI
);
192 // Pull the next byte
193 byte ch
= b
[i
+ off
];
195 if (master
.binaryMode
== true) {
197 if (ch
== TELNET_IAC
) {
199 writeBuffer
[writeBufferI
++] = (byte) TELNET_IAC
;
200 writeBuffer
[writeBufferI
++] = (byte) TELNET_IAC
;
202 // Anything else -> just send
203 writeBuffer
[writeBufferI
++] = ch
;
208 // Non-binary mode: more complicated. We use writeCR to handle
209 // the case that the last byte of b was a CR.
211 // Bare carriage return -> CR NUL
213 if (writeCR
== true) {
214 // Flush the previous CR to the stream.
215 // CR <anything> -> CR NULL
216 writeBuffer
[writeBufferI
++] = (byte) C_CR
;
217 writeBuffer
[writeBufferI
++] = (byte) C_NUL
;
220 } else if (ch
== C_LF
) {
221 if (writeCR
== true) {
223 writeBuffer
[writeBufferI
++] = (byte) C_CR
;
224 writeBuffer
[writeBufferI
++] = (byte) C_LF
;
228 writeBuffer
[writeBufferI
++] = ch
;
230 } else if (ch
== TELNET_IAC
) {
231 if (writeCR
== true) {
232 // CR <anything> -> CR NULL
233 writeBuffer
[writeBufferI
++] = (byte) C_CR
;
234 writeBuffer
[writeBufferI
++] = (byte) C_NUL
;
238 writeBuffer
[writeBufferI
++] = (byte) TELNET_IAC
;
239 writeBuffer
[writeBufferI
++] = (byte) TELNET_IAC
;
241 if (writeCR
== true) {
242 // CR <anything> -> CR NULL
243 writeBuffer
[writeBufferI
++] = (byte) C_CR
;
244 writeBuffer
[writeBufferI
++] = (byte) C_NUL
;
247 // Normal character */
248 writeBuffer
[writeBufferI
++] = ch
;
252 } // while (i < userbuf.length)
254 if (writeBufferI
> 0) {
255 // Flush what we have generated so far and reset the buffer.
256 output
.write(writeBuffer
, 0, writeBufferI
);