001 /* 002 * Copyright (C) 2003-2009 eXo Platform SAS. 003 * 004 * This is free software; you can redistribute it and/or modify it 005 * under the terms of the GNU Lesser General Public License as 006 * published by the Free Software Foundation; either version 2.1 of 007 * the License, or (at your option) any later version. 008 * 009 * This software is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public 015 * License along with this software; if not, write to the Free 016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 018 */ 019 020 package org.crsh.term.console; 021 022 import java.io.IOException; 023 import java.util.LinkedList; 024 import java.util.NoSuchElementException; 025 026 /** 027 * <p>This class provides an abstraction for a console. This implementation wraps the input and output of a terminal 028 * based on a bidirectional io.</p> 029 * 030 * <p>Interactions between terminal and console are done though the {@link ViewReader} and {@link ViewWriter} 031 * classes.</p> 032 * 033 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> 034 * @version $Revision$ 035 */ 036 public final class Console { 037 038 /** . */ 039 private char[] buffer; 040 041 /** . */ 042 private int size; 043 044 /** Cursor Position, always equal to {@link #size} unless the underlying *.IO class supports editing. */ 045 private int curAt; 046 047 /** . */ 048 private LinkedList<CharSequence> lines; 049 050 /** Do we have a issued a CR previously? */ 051 private boolean previousCR; 052 053 /** Whether or not we do echoing. */ 054 private boolean echoing; 055 056 /** . */ 057 private final ViewWriter viewWriter; 058 059 /** . */ 060 private final ViewReader viewReader = new ViewReader() { 061 062 @Override 063 public CharSequence replace(CharSequence s) throws IOException { 064 StringBuilder builder = new StringBuilder(); 065 boolean flush = false; 066 for (int i = appendDel();i != -1;i = appendDel()) { 067 builder.append((char)i); 068 flush = true; 069 } 070 flush |= appendData(s, 0, s.length()); 071 if (flush) { 072 viewWriter.flush(); 073 } 074 return builder.reverse().toString(); 075 } 076 077 @Override 078 public ViewReader append(char c) throws IOException { 079 if (appendData(c)) { 080 viewWriter.flush(); 081 } 082 return this; 083 } 084 085 @Override 086 public ViewReader append(CharSequence s) throws IOException { 087 return append(s, 0, s.length()); 088 } 089 090 @Override 091 public ViewReader append(CharSequence csq, int start, int end) throws IOException { 092 if (appendData(csq, start, end)) { 093 viewWriter.flush(); 094 } 095 return this; 096 } 097 098 @Override 099 public int del() throws IOException { 100 int ret = appendDel(); 101 if (ret != -1) { 102 viewWriter.flush(); 103 } 104 return ret; 105 } 106 107 @Override 108 public boolean moveRight() throws IOException { 109 return Console.this.moveRight(); 110 } 111 112 @Override 113 public boolean moveLeft() throws IOException { 114 return Console.this.moveLeft(); 115 } 116 }; 117 118 /** . */ 119 private final ConsoleReader reader = new ConsoleReader() { 120 @Override 121 public int getSize() { 122 return size; 123 } 124 125 @Override 126 public boolean hasNext() { 127 return lines.size() > 0; 128 } 129 130 @Override 131 public CharSequence next() { 132 if (lines.size() > 0) { 133 return lines.removeFirst(); 134 } else { 135 throw new NoSuchElementException(); 136 } 137 } 138 }; 139 140 /** . */ 141 private final ConsoleWriter writer = new ConsoleWriter() { 142 143 // 144 private boolean previousCR; 145 146 @Override 147 public void write(CharSequence s) throws IOException { 148 for (int i = 0;i < s.length();i++) { 149 char c = s.charAt(i); 150 writeNoFlush(c); 151 } 152 viewWriter.flush(); 153 } 154 155 public void write(char c) throws IOException { 156 writeNoFlush(c); 157 viewWriter.flush(); 158 } 159 160 private void writeNoFlush(char c) throws IOException { 161 if (previousCR && c == '\n') { 162 previousCR = false; 163 } else if (c == '\r' || c == '\n') { 164 previousCR = c == '\r'; 165 viewWriter.writeCRLF(); 166 } else { 167 viewWriter.write(c); 168 } 169 } 170 }; 171 172 public Console(ViewWriter viewWriter) { 173 this.buffer = new char[128]; 174 this.size = 0; 175 this.curAt = 0; 176 this.lines = new LinkedList<CharSequence>(); 177 this.previousCR = false; 178 this.echoing = true; 179 this.viewWriter = viewWriter; 180 } 181 182 /** 183 * Clears the buffer without doing any echoing. 184 */ 185 public void clearBuffer() { 186 this.previousCR = false; 187 this.curAt = 0; 188 this.size = 0; 189 } 190 191 public CharSequence getBuffer() { 192 return new String(buffer, 0, size); 193 } 194 195 public CharSequence getBufferToCursor() { 196 return new String(buffer, 0, curAt); 197 } 198 199 public boolean isEchoing() { 200 return echoing; 201 } 202 203 public void setEchoing(boolean echoing) { 204 Console.this.echoing = echoing; 205 } 206 207 /** 208 * Returns the console reader. 209 * 210 * @return the console reader 211 */ 212 public ConsoleReader getReader() { 213 return reader; 214 } 215 216 public ViewReader getViewReader() { 217 return viewReader; 218 } 219 220 public ConsoleWriter getWriter() { 221 return writer; 222 } 223 224 private boolean appendData(CharSequence s, int start, int end) throws IOException { 225 if (start < 0) { 226 throw new IndexOutOfBoundsException("No negative start"); 227 } 228 if (end < 0) { 229 throw new IndexOutOfBoundsException("No negative end"); 230 } 231 if (end > s.length()) { 232 throw new IndexOutOfBoundsException("End cannot be greater than sequence length"); 233 } 234 if (end < start) { 235 throw new IndexOutOfBoundsException("Start cannot be greater than end"); 236 } 237 boolean flush = false; 238 for (int i = start;i < end;i++) { 239 flush |= appendData(s.charAt(i)); 240 } 241 return flush; 242 } 243 244 /** 245 * Append a char at the current cursor position and increment the cursor position. 246 * 247 * @param c the char to append 248 * @return true if flush is required 249 * @throws IOException any IOException 250 */ 251 private boolean appendData(char c) throws IOException { 252 if (previousCR && c == '\n') { 253 previousCR = false; 254 return false; 255 } else if (c == '\r' || c == '\n') { 256 previousCR = c == '\r'; 257 String line = new String(buffer, 0, size); 258 lines.add(line); 259 size = 0; 260 curAt = size; 261 return echoCRLF(); 262 } else { 263 if (push(c)) { 264 return echo(c); 265 } else { 266 String disp = new String(buffer, curAt, size - curAt); 267 viewWriter.write(disp); 268 int amount = size - curAt - 1; 269 curAt++; 270 while (amount > 0) { 271 viewWriter.writeMoveLeft(); 272 amount--; 273 } 274 return true; 275 } 276 } 277 } 278 279 /** 280 * Delete the char before the cursor. 281 * 282 * @return the removed char value or -1 if no char was removed 283 * @throws IOException any IOException 284 */ 285 private int appendDel() throws IOException { 286 287 // If the cursor is at the most right position (i.e no more chars after) 288 if (curAt == size){ 289 int popped = pop(); 290 291 // 292 if (popped != -1) { 293 echoDel(); 294 // We do not care about the return value of echoDel, but we will return a value that indcates 295 // that a flush is required although it may not 296 // to properly carry out the status we should have two things to return 297 // 1/ the popped char 298 // 2/ the boolean indicating if flush is required 299 } 300 301 // 302 return popped; 303 } else { 304 // We are editing the line 305 306 // Shift all the chars after the cursor 307 int popped = pop(); 308 309 // 310 if (popped != -1) { 311 312 // We move the cursor to left 313 if (viewWriter.writeMoveLeft()) { 314 StringBuilder disp = new StringBuilder(); 315 disp.append(buffer, curAt, size - curAt); 316 disp.append(' '); 317 viewWriter.write(disp); 318 int amount = size - curAt + 1; 319 while (amount > 0) { 320 viewWriter.writeMoveLeft(); 321 amount--; 322 } 323 } else { 324 throw new UnsupportedOperationException("not implemented"); 325 } 326 } 327 328 // 329 return popped; 330 } 331 } 332 333 private boolean moveRight() throws IOException { 334 if (curAt < size && viewWriter.writeMoveRight(buffer[curAt])) { 335 viewWriter.flush(); 336 curAt++; 337 return true; 338 } else { 339 return false; 340 } 341 } 342 343 private boolean moveLeft() throws IOException { 344 boolean moved = curAt > 0 && viewWriter.writeMoveLeft(); 345 if (moved) { 346 viewWriter.flush(); 347 curAt--; 348 } 349 return moved; 350 } 351 352 private boolean echo(char c) throws IOException { 353 if (echoing) { 354 viewWriter.write(c); 355 return true; 356 } else { 357 return false; 358 } 359 } 360 361 private void echo(String s) throws IOException { 362 if (echoing) { 363 viewWriter.write(s); 364 viewWriter.flush(); 365 } 366 } 367 368 private boolean echoDel() throws IOException { 369 if (echoing) { 370 viewWriter.writeDel(); 371 return true; 372 } else { 373 return false; 374 } 375 } 376 377 private boolean echoCRLF() throws IOException { 378 if (echoing) { 379 viewWriter.writeCRLF(); 380 return true; 381 } else { 382 return false; 383 } 384 } 385 386 /** 387 * Popup one char from buffer at the current cursor position. 388 * 389 * @return the popped char or -1 if none was removed 390 */ 391 private int pop() { 392 if (curAt > 0) { 393 char popped = buffer[curAt - 1]; 394 if (curAt == size) { 395 buffer[curAt] = 0; 396 size = --curAt; 397 return popped; 398 } else { 399 for (int i = curAt;i < size;i++) { 400 buffer[i - 1] = buffer[i]; 401 } 402 buffer[--size] = 0; 403 curAt--; 404 } 405 return popped; 406 } else { 407 return -1; 408 } 409 } 410 411 /** 412 * Push one char in the buffer at the current cursor position. This operation ensures that the buffer 413 * is large enough and it may increase the buffer capacity when required. The cursor position is incremented 414 * when a char is appended at the last position, otherwise the cursor position remains unchanged. 415 * 416 * @param c the char to push 417 * @return true if the cursor position was incremented 418 */ 419 private boolean push(char c) { 420 if (size >= buffer.length) { 421 char[] tmp = new char[buffer.length * 2 + 1]; 422 System.arraycopy(buffer, 0, tmp, 0, buffer.length); 423 Console.this.buffer = tmp; 424 } 425 if (curAt == size) { 426 buffer[size++] = c; 427 curAt++; 428 return true; 429 } else { 430 for (int i = size - 1;i > curAt - 1;i--) { 431 buffer[i + 1] = buffer[i]; 432 } 433 buffer[curAt] = c; 434 ++size; 435 return false; 436 } 437 } 438 }