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