color chooser widget
[fanfix.git] / src / jexer / net / TelnetOutputStream.java
CommitLineData
daa4106c 1/*
ea91242c
KL
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31package jexer.net;
32
33import java.io.OutputStream;
34import java.io.IOException;
35
0d47c546
KL
36import static jexer.net.TelnetSocket.*;
37
ea91242c
KL
38/**
39 * TelnetOutputStream works with TelnetSocket to perform the telnet protocol.
40 */
41public final class TelnetOutputStream extends OutputStream {
42
43 /**
44 * The root TelnetSocket that has my telnet protocol state.
45 */
46 private TelnetSocket master;
47
48 /**
49 * The raw socket's OutputStream.
50 */
51 private OutputStream output;
52
53 /**
54 * Package private constructor.
55 *
56 * @param master the master TelnetSocket
57 * @param output the underlying socket's OutputStream
58 */
9b1afdde 59 TelnetOutputStream(final TelnetSocket master, final OutputStream output) {
ea91242c
KL
60 this.master = master;
61 this.output = output;
62 }
63
64 // OutputStream interface -------------------------------------------------
65
66 /**
67 * Closes this output stream and releases any system resources associated
68 * with this stream.
9b1afdde
KL
69 *
70 * @throws IOException if an I/O error occurs
ea91242c
KL
71 */
72 @Override
73 public void close() throws IOException {
005ec497
KL
74 if (output != null) {
75 output.close();
76 output = null;
77 }
ea91242c
KL
78 }
79
80 /**
81 * Flushes this output stream and forces any buffered output bytes to be
82 * written out.
9b1afdde
KL
83 *
84 * @throws IOException if an I/O error occurs
ea91242c
KL
85 */
86 @Override
87 public void flush() throws IOException {
9b1afdde 88 if ((master.binaryMode == false) && (writeCR == true)) {
005ec497
KL
89 // The last byte sent to this.write() was a CR, which was never
90 // actually sent. So send the CR in ascii mode, then flush.
91 // CR <anything> -> CR NULL
0d47c546
KL
92 output.write(C_CR);
93 output.write(C_NUL);
9b1afdde 94 writeCR = false;
005ec497
KL
95 }
96 output.flush();
ea91242c
KL
97 }
98
99 /**
100 * Writes b.length bytes from the specified byte array to this output
101 * stream.
9b1afdde
KL
102 *
103 * @param b the data.
104 * @throws IOException if an I/O error occurs
ea91242c
KL
105 */
106 @Override
9b1afdde 107 public void write(final byte[] b) throws IOException {
005ec497 108 writeImpl(b, 0, b.length);
ea91242c
KL
109 }
110
111 /**
112 * Writes len bytes from the specified byte array starting at offset off
113 * to this output stream.
9b1afdde
KL
114 *
115 * @param b the data.
116 * @param off the start offset in the data.
117 * @param len the number of bytes to write.
118 * @throws IOException if an I/O error occurs
ea91242c
KL
119 */
120 @Override
9b1afdde
KL
121 public void write(final byte[] b, final int off,
122 final int len) throws IOException {
123
005ec497 124 writeImpl(b, off, len);
ea91242c
KL
125 }
126
127 /**
128 * Writes the specified byte to this output stream.
9b1afdde
KL
129 *
130 * @param b the byte to write.
131 * @throws IOException if an I/O error occurs
ea91242c
KL
132 */
133 @Override
9b1afdde 134 public void write(final int b) throws IOException {
005ec497
KL
135 byte [] bytes = new byte[1];
136 bytes[0] = (byte)b;
137 writeImpl(bytes, 0, 1);
ea91242c
KL
138 }
139
005ec497
KL
140 /**
141 * Writes b.length bytes from the specified byte array to this output
142 * stream. Note package private access.
9b1afdde
KL
143 *
144 * @param b the data.
145 * @throws IOException if an I/O error occurs
005ec497 146 */
9b1afdde 147 void rawWrite(final byte[] b) throws IOException {
005ec497
KL
148 output.write(b, 0, b.length);
149 }
150
9b1afdde
KL
151 // Telnet protocol --------------------------------------------------------
152
153 /**
154 * When true, the last byte the caller passed to write() was a CR.
155 */
156 private boolean writeCR = false;
157
005ec497
KL
158 /**
159 * Writes len bytes from the specified byte array starting at offset off
160 * to this output stream.
9b1afdde
KL
161 *
162 * @param b the data.
163 * @param off the start offset in the data.
164 * @param len the number of bytes to write.
165 * @throws IOException if an I/O error occurs
005ec497
KL
166 */
167 private void writeImpl(final byte[] b, final int off,
168 final int len) throws IOException {
169
170 byte [] writeBuffer = new byte[Math.max(len, 4)];
171 int writeBufferI = 0;
172
173 for (int i = 0; i < len; i++) {
174 if (writeBufferI >= writeBuffer.length - 4) {
175 // Flush what we have generated so far and reset the buffer,
176 // because the next byte could generate up to 4 output bytes
177 // (CR <something> <IAC> <IAC>).
9b1afdde 178 output.write(writeBuffer, 0, writeBufferI);
005ec497
KL
179 writeBufferI = 0;
180 }
181
182 // Pull the next byte
183 byte ch = b[i + off];
184
9b1afdde 185 if (master.binaryMode == true) {
005ec497 186
0d47c546 187 if (ch == TELNET_IAC) {
005ec497 188 // IAC -> IAC IAC
0d47c546
KL
189 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
190 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
005ec497
KL
191 } else {
192 // Anything else -> just send
193 writeBuffer[writeBufferI++] = ch;
194 }
195 continue;
196 }
197
198 // Non-binary mode: more complicated. We use writeCR to handle
199 // the case that the last byte of b was a CR.
200
201 // Bare carriage return -> CR NUL
0d47c546 202 if (ch == C_CR) {
9b1afdde 203 if (writeCR == true) {
005ec497
KL
204 // Flush the previous CR to the stream.
205 // CR <anything> -> CR NULL
0d47c546
KL
206 writeBuffer[writeBufferI++] = (byte)C_CR;
207 writeBuffer[writeBufferI++] = (byte)C_NUL;
005ec497 208 }
9b1afdde 209 writeCR = true;
0d47c546 210 } else if (ch == C_LF) {
9b1afdde 211 if (writeCR == true) {
005ec497 212 // CR LF -> CR LF
0d47c546
KL
213 writeBuffer[writeBufferI++] = (byte)C_CR;
214 writeBuffer[writeBufferI++] = (byte)C_LF;
9b1afdde 215 writeCR = false;
005ec497
KL
216 } else {
217 // Bare LF -> LF
218 writeBuffer[writeBufferI++] = ch;
219 }
0d47c546 220 } else if (ch == TELNET_IAC) {
9b1afdde 221 if (writeCR == true) {
005ec497 222 // CR <anything> -> CR NULL
0d47c546
KL
223 writeBuffer[writeBufferI++] = (byte)C_CR;
224 writeBuffer[writeBufferI++] = (byte)C_NUL;
9b1afdde 225 writeCR = false;
005ec497
KL
226 }
227 // IAC -> IAC IAC
0d47c546
KL
228 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
229 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
005ec497 230 } else {
9b1afdde 231 if (writeCR == true) {
005ec497 232 // CR <anything> -> CR NULL
0d47c546
KL
233 writeBuffer[writeBufferI++] = (byte)C_CR;
234 writeBuffer[writeBufferI++] = (byte)C_NUL;
9b1afdde 235 writeCR = false;
005ec497
KL
236 } else {
237 // Normal character */
238 writeBuffer[writeBufferI++] = ch;
239 }
240 }
241
242 } // while (i < userbuf.length)
243
244 if (writeBufferI > 0) {
245 // Flush what we have generated so far and reset the buffer.
9b1afdde 246 output.write(writeBuffer, 0, writeBufferI);
005ec497
KL
247 }
248 }
249
ea91242c 250}