org.crsh.telnet.term.console.TermIOBuffer Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2012 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.crsh.telnet.term.console;
import org.crsh.telnet.term.spi.TermIO;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
public final class TermIOBuffer implements Appendable, Iterator {
/** . */
private char[] buffer;
/** . */
private int size;
/** Cursor Position, always equal to {@link #size} unless the underlying *.IO class supports editing. */
private int curAt;
/** . */
private LinkedList lines;
/** Do we have a issued a CR previously? */
private boolean previousCR;
/** Whether or not we do echoing. */
private boolean echoing;
/** . */
private final TermIO io;
public TermIOBuffer(TermIO io) {
this.buffer = new char[128];
this.size = 0;
this.curAt = 0;
this.lines = new LinkedList();
this.previousCR = false;
this.echoing = true;
this.io = io;
}
/**
* Clears the buffer without doing any echoing.
*/
public void clear() {
this.previousCR = false;
this.curAt = 0;
this.size = 0;
}
/**
* Returns the total number of chars in the buffer, independently of the cursor position.
*
* @return the number of chars
*/
public int getSize() {
return size;
}
/**
* Returns the current cursor position.
*
* @return the cursor position
*/
int getCursor() {
return curAt;
}
/**
* Returns a character at a specified index in the buffer.
*
* @param index the index
* @return the char
* @throws IndexOutOfBoundsException if the index is negative or larget than the size
*/
char charAt(int index) throws IndexOutOfBoundsException {
if (index < 0) {
throw new IndexOutOfBoundsException("No negative position accepted");
}
if (index >= size) {
throw new IndexOutOfBoundsException("Cannot accept position greater than size:" + index + " >= " + size);
}
return buffer[index];
}
CharSequence getBufferToCursor() {
return new String(buffer, 0, curAt);
}
boolean isEchoing() {
return echoing;
}
void setEchoing(boolean echoing) {
this.echoing = echoing;
}
// Iterator implementation *****************************************************************************
public boolean hasNext() {
return lines.size() > 0;
}
public CharSequence next() {
if (lines.size() > 0) {
return lines.removeFirst();
} else {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
// Appendable implementation *****************************************************************************************
public TermIOBuffer append(char c) throws IOException {
if (appendData(c)) {
io.flush();
}
return this;
}
public TermIOBuffer append(CharSequence s) throws IOException {
return append(s, 0, s.length());
}
public TermIOBuffer append(CharSequence csq, int start, int end) throws IOException {
if (appendData(csq, start, end)) {
io.flush();
}
return this;
}
// Protected methods *************************************************************************************************
/**
* Replace all the characters before the cursor by the provided char sequence.
*
* @param s the new char sequence
* @return the l
* @throws IOException any IOException
*/
CharSequence replace(CharSequence s) throws IOException {
StringBuilder builder = new StringBuilder();
boolean flush = false;
for (int i = appendDel();i != -1;i = appendDel()) {
builder.append((char)i);
flush = true;
}
flush |= appendData(s, 0, s.length());
if (flush) {
io.flush();
}
return builder.reverse().toString();
}
public boolean moveRight() throws IOException {
return moveRight(1) == 1;
}
public boolean moveLeft() throws IOException {
return moveLeft(1) == 1;
}
public int moveRight(int count) throws IOException, IllegalArgumentException {
if (count < 0) {
throw new IllegalArgumentException("Cannot move with negative count " + count);
}
int delta = 0;
while (delta < count) {
if (curAt + delta < size && io.moveRight(buffer[curAt + delta])) {
delta++;
} else {
break;
}
}
if (delta > 0) {
io.flush();
curAt += delta;
}
return delta;
}
int moveLeft(int count) throws IOException, IllegalArgumentException {
if (count < 0) {
throw new IllegalArgumentException("Cannot move with negative count " + count);
}
int delta = 0;
while (delta < count) {
if (delta < curAt && io.moveLeft()) {
delta++;
} else {
break;
}
}
if (delta > 0) {
io.flush();
curAt -= delta;
}
return delta;
}
/**
* Delete the char under the cursor or return -1 if no char was deleted.
*
* @return the deleted char
* @throws IOException any IOException
*/
public int del() throws IOException {
int ret = appendDel();
if (ret != -1) {
io.flush();
}
return ret;
}
private boolean appendData(CharSequence s, int start, int end) throws IOException {
if (start < 0) {
throw new IndexOutOfBoundsException("No negative start");
}
if (end < 0) {
throw new IndexOutOfBoundsException("No negative end");
}
if (end > s.length()) {
throw new IndexOutOfBoundsException("End cannot be greater than sequence length");
}
if (end < start) {
throw new IndexOutOfBoundsException("Start cannot be greater than end");
}
boolean flush = false;
for (int i = start;i < end;i++) {
flush |= appendData(s.charAt(i));
}
return flush;
}
/**
* Append a char at the current cursor position and increment the cursor position.
*
* @param c the char to append
* @return true if flush is required
* @throws IOException any IOException
*/
private boolean appendData(char c) throws IOException {
if (previousCR && c == '\n') {
previousCR = false;
return false;
} else if (c == '\r' || c == '\n') {
previousCR = c == '\r';
String line = new String(buffer, 0, size);
lines.add(line);
size = 0;
curAt = size;
return echoCRLF();
} else {
if (push(c)) {
return echo(c);
} else {
String disp = new String(buffer, curAt, size - curAt);
io.write(disp);
int amount = size - curAt - 1;
curAt++;
while (amount > 0) {
io.moveLeft();
amount--;
}
return true;
}
}
}
/**
* Delete the char before the cursor.
*
* @return the removed char value or -1 if no char was removed
* @throws IOException any IOException
*/
private int appendDel() throws IOException {
// If the cursor is at the most right position (i.e no more chars after)
if (curAt == size){
int popped = pop();
//
if (popped != -1) {
echoDel();
// We do not care about the return value of echoDel, but we will return a value that indcates
// that a flush is required although it may not
// to properly carry out the status we should have two things to return
// 1/ the popped char
// 2/ the boolean indicating if flush is required
}
//
return popped;
} else {
// We are editing the line
// Shift all the chars after the cursor
int popped = pop();
//
if (popped != -1) {
// We move the cursor to left
if (io.moveLeft()) {
StringBuilder disp = new StringBuilder();
disp.append(buffer, curAt, size - curAt);
disp.append(' ');
io.write(disp);
int amount = size - curAt + 1;
while (amount > 0) {
io.moveLeft();
amount--;
}
} else {
throw new UnsupportedOperationException("not implemented");
}
}
//
return popped;
}
}
private boolean echo(char c) throws IOException {
if (echoing) {
io.write(c);
return true;
} else {
return false;
}
}
private void echo(String s) throws IOException {
if (echoing) {
io.write(s);
io.flush();
}
}
private boolean echoDel() throws IOException {
if (echoing) {
io.writeDel();
return true;
} else {
return false;
}
}
private boolean echoCRLF() throws IOException {
if (echoing) {
io.writeCRLF();
return true;
} else {
return false;
}
}
/**
* Popup one char from buffer at the current cursor position.
*
* @return the popped char or -1 if none was removed
*/
private int pop() {
if (curAt > 0) {
char popped = buffer[curAt - 1];
if (curAt == size) {
buffer[curAt] = 0;
size = --curAt;
return popped;
} else {
for (int i = curAt;i < size;i++) {
buffer[i - 1] = buffer[i];
}
buffer[--size] = 0;
curAt--;
}
return popped;
} else {
return -1;
}
}
/**
* Push one char in the buffer at the current cursor position. This operation ensures that the buffer
* is large enough and it may increase the buffer capacity when required. The cursor position is incremented
* when a char is appended at the last position, otherwise the cursor position remains unchanged.
*
* @param c the char to push
* @return true if the cursor position was incremented
*/
private boolean push(char c) {
if (size >= buffer.length) {
char[] tmp = new char[buffer.length * 2 + 1];
System.arraycopy(buffer, 0, tmp, 0, buffer.length);
TermIOBuffer.this.buffer = tmp;
}
if (curAt == size) {
buffer[size++] = c;
curAt++;
return true;
} else {
for (int i = size - 1;i > curAt - 1;i--) {
buffer[i + 1] = buffer[i];
}
buffer[curAt] = c;
++size;
return false;
}
}
}