class TelnetClientIACFilterer extends InputStream {
        private final NegotiationState negotiationState;
        private final InputStream inputStream;
        private final byte[] buffer;
        private final byte[] workingBuffer;
        private int bytesInBuffer;
        private TelnetClientEventListener eventListener;
        private Socket socket;

        TelnetClientIACFilterer(Socket socket) throws IOException {
            this.negotiationState = new NegotiationState();
            this.inputStream = socket.getInputStream();
            this.buffer = new byte[64 * 1024];
            this.workingBuffer = new byte[1024];
            this.bytesInBuffer = 0;
            this.eventListener = null;
            this.socket = socket;
        }

        private void setEventListener(TelnetClientEventListener eventListener) {
            this.eventListener = eventListener;
        }

        @Override
        public int read() {
            throw new UnsupportedOperationException("TelnetClientIACFilterer doesn't support .read()");
        }

        @Override
        public void close() throws IOException {
            inputStream.close();
        }

        @Override
        public int available() throws IOException {
            if(bytesInBuffer > 0) {
                return bytesInBuffer;
            }
            fillBuffer(false);
            return Math.abs(bytesInBuffer);
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            if (bytesInBuffer == -1) {
                return -1;
            }
            if(available() == 0) {
               // There was nothing in the buffer and the underlying
               // stream has nothing available, so do a blocking read
               // from the stream.
               fillBuffer(true);
            }
            if(bytesInBuffer <= 0) {
                return -1;
            }
            int bytesToCopy = Math.min(len, bytesInBuffer);
            System.arraycopy(buffer, 0, b, off, bytesToCopy);
            System.arraycopy(buffer, bytesToCopy, buffer, 0, buffer.length - bytesToCopy);
            bytesInBuffer -= bytesToCopy;
            return bytesToCopy;
        }

        private void fillBuffer(boolean block) throws IOException {
            int maxFill = Math.min(workingBuffer.length, buffer.length - bytesInBuffer);

            int oldTimeout = socket.getSoTimeout();
            if (!block) { socket.setSoTimeout(1); }
            int readBytes;
            try {
                readBytes = inputStream.read(workingBuffer, 0, maxFill);
            } catch (java.net.SocketTimeoutException ste) {
                readBytes = 0;
            }
            if (!block) { socket.setSoTimeout(oldTimeout); }

            if (readBytes == -1) {
                bytesInBuffer = -1; return;
            }
            for(int i = 0; i < readBytes; i++) {
                if(workingBuffer[i] == COMMAND_IAC) {
                    i++;
                    if(Arrays.asList(COMMAND_DO, COMMAND_DONT, COMMAND_WILL, COMMAND_WONT).contains(workingBuffer[i])) {
                        parseCommand(workingBuffer, i, readBytes);
                        ++i;
                        continue;
                    }
                    else if(workingBuffer[i] == COMMAND_SUBNEGOTIATION) {   //0xFA = SB = Subnegotiation
                        i += parseSubNegotiation(workingBuffer, ++i, readBytes);
                        continue;
                    }
                    else if(workingBuffer[i] != COMMAND_IAC) {   //Double IAC = 255
                        System.err.println("Unknown Telnet command: " + workingBuffer[i]);
                    }
                }
                buffer[bytesInBuffer++] = workingBuffer[i];
            }
        }
        
        private void parseCommand(byte[] buffer, int position, int max) throws IOException {<FILL_FUNCTION_BODY>}
        
        private int parseSubNegotiation(byte[] buffer, int position, int max) {
            int originalPosition = position;

            //Read operation
            byte operation = buffer[position++];
            
            //Read until [IAC SE]            
            ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
            while(position < max) {
                byte read = buffer[position];
                if(read != COMMAND_IAC) {
                    outputBuffer.write(read);
                }
                else {
                    if(position + 1 == max) {
                        throw new IllegalStateException("State error, unexpected end of buffer when reading subnegotiation");
                    }
                    position++;
                    if(buffer[position] == COMMAND_IAC) {
                        outputBuffer.write(COMMAND_IAC);    //Escaped IAC
                    }
                    else if(buffer[position] == COMMAND_SUBNEGOTIATION_END) {
                        parseSubNegotiation(operation, outputBuffer.toByteArray());
                        return ++position - originalPosition;
                    }
                }
                position++;
            }
            throw new IllegalStateException("State error, unexpected end of buffer when reading subnegotiation, no IAC SE");
        }

        private void parseSubNegotiation(byte option, byte[] additionalData) {
            switch(option) {
                case OPTION_NAWS:
                    eventListener.onResize(
                            convertTwoBytesToInt2(additionalData[1], additionalData[0]), 
                            convertTwoBytesToInt2(additionalData[3], additionalData[2]));
                    break;
                case OPTION_LINEMODE:
                    //We don't parse this, as this is a very complicated command :(
                    //Let's leave it for now, fingers crossed
                    break;
                default:
                    negotiationState.onUnsupportedSubnegotiation(option, additionalData);
                    break;
            }
        }
    }

            if(position + 1 >= max) {
                throw new IllegalStateException("State error, we got a command signal from the remote telnet client but "
                        + "not enough characters available in the stream");
            }
            byte command = buffer[position];
            byte value = buffer[position + 1];
            switch(command) {
                case COMMAND_DO:
                case COMMAND_DONT:
                    if(value == OPTION_SUPPRESS_GO_AHEAD) {
                        negotiationState.suppressGoAhead = (command == COMMAND_DO);
                        eventListener.requestReply(command == COMMAND_DO, value);
                    }
                    else if(value == OPTION_EXTEND_ASCII) {
                        negotiationState.extendedAscii = (command == COMMAND_DO);
                        eventListener.requestReply(command == COMMAND_DO, value);
                    }
                    else {
                        negotiationState.onUnsupportedRequestCommand(command == COMMAND_DO, value);
                    }
                    break;
                case COMMAND_WILL:
                case COMMAND_WONT:
                    if(value == OPTION_ECHO) {
                        negotiationState.clientEcho = (command == COMMAND_WILL);
                    }
                    else if(value == OPTION_LINEMODE) {
                        negotiationState.clientLineMode0 = (command == COMMAND_WILL);
                    }
                    else if(value == OPTION_NAWS) {
                       negotiationState.clientResizeNotification = (command == COMMAND_WILL);
                    }
                    else {
                        negotiationState.onUnsupportedStateCommand(command == COMMAND_WILL, value);
                    }
                    break;
                default:
                    throw new UnsupportedOperationException("No command handler implemented for " + TelnetProtocol.CODE_TO_NAME.get(command));
            }
        
<methods>public void clearScreen() throws java.io.IOException,public void close() throws java.io.IOException,public void deiconify() throws java.io.IOException,public void disableSGR(com.googlecode.lanterna.SGR) throws java.io.IOException,public void enableSGR(com.googlecode.lanterna.SGR) throws java.io.IOException,public void enterPrivateMode() throws java.io.IOException,public void exitPrivateMode() throws java.io.IOException,public synchronized com.googlecode.lanterna.TerminalPosition getCursorPosition() throws java.io.IOException,public final synchronized com.googlecode.lanterna.TerminalSize getTerminalSize() throws java.io.IOException,public void iconify() throws java.io.IOException,public void maximize() throws java.io.IOException,public com.googlecode.lanterna.input.KeyStroke pollInput() throws java.io.IOException,public void popTitle() ,public void pushTitle() ,public com.googlecode.lanterna.input.KeyStroke readInput() throws java.io.IOException,public void resetColorAndSGR() throws java.io.IOException,public void scrollLines(int, int, int) throws java.io.IOException,public void setBackgroundColor(com.googlecode.lanterna.TextColor) throws java.io.IOException,public void setCursorPosition(int, int) throws java.io.IOException,public void setCursorPosition(com.googlecode.lanterna.TerminalPosition) throws java.io.IOException,public void setCursorVisible(boolean) throws java.io.IOException,public void setForegroundColor(com.googlecode.lanterna.TextColor) throws java.io.IOException,public void setMouseCaptureMode(com.googlecode.lanterna.terminal.MouseCaptureMode) throws java.io.IOException,public void setTerminalSize(int, int) throws java.io.IOException,public void setTitle(java.lang.String) throws java.io.IOException,public void unmaximize() throws java.io.IOException<variables>private boolean inPrivateMode,private com.googlecode.lanterna.terminal.MouseCaptureMode mouseCaptureMode,private com.googlecode.lanterna.terminal.MouseCaptureMode requestedMouseCaptureMode