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          for (int i = appendDel();i != -1;i = appendDel()) {
066            builder.append((char)i);
067          }
068          appendData(s);
069          return builder.reverse().toString();
070        }
071    
072        @Override
073        public void write(char c) throws IOException {
074          appendData(c);
075        }
076    
077        @Override
078        public void write(CharSequence s) throws IOException {
079          appendData(s.toString());
080        }
081    
082        @Override
083        public void del() throws IOException {
084          appendDel();
085        }
086    
087        @Override
088        public void moveRight() throws IOException {
089          Console.this.moveRight();
090        }
091    
092        @Override
093        public void moveLeft() throws IOException {
094          Console.this.moveLeft();
095        }
096      };
097    
098      /** . */
099      private final ConsoleReader reader = new ConsoleReader() {
100        @Override
101        public int getSize() {
102          return size;
103        }
104    
105        @Override
106        public boolean hasNext() {
107          return lines.size() > 0;
108        }
109    
110        @Override
111        public CharSequence next() {
112          if (lines.size() > 0) {
113            return lines.removeFirst();
114          } else {
115            throw new NoSuchElementException();
116          }
117        }
118      };
119    
120      /** . */
121      private final ConsoleWriter writer = new ConsoleWriter() {
122    
123        //
124        private boolean previousCR;
125    
126        @Override
127        public void write(CharSequence s) throws IOException {
128          for (int i = 0;i < s.length();i++) {
129            char c = s.charAt(i);
130            writeNoFlush(c);
131          }
132          viewWriter.flush();
133        }
134    
135        public void write(char c) throws IOException {
136          writeNoFlush(c);
137          viewWriter.flush();
138        }
139    
140        private void writeNoFlush(char c) throws IOException {
141          if (previousCR && c == '\n') {
142            previousCR = false;
143          } else if (c == '\r' || c == '\n') {
144            previousCR = c == '\r';
145            viewWriter.writeCRLF();
146          } else {
147            viewWriter.write(c);
148          }
149        }
150      };
151    
152      public Console(ViewWriter viewWriter) {
153        this.buffer = new char[128];
154        this.size = 0;
155        this.curAt = 0;
156        this.lines = new LinkedList<CharSequence>();
157        this.previousCR = false;
158        this.echoing = true;
159        this.viewWriter = viewWriter;
160      }
161    
162      /**
163       * Clears the buffer without doing any echoing.
164       */
165      public void clearBuffer() {
166        this.previousCR = false;
167        this.curAt = 0;
168        this.size = 0;
169      }
170    
171      public CharSequence getBuffer() {
172        return new String(buffer, 0, size);
173      }
174    
175      public CharSequence getBufferToCursor() {
176        return new String(buffer, 0, curAt);
177      }
178    
179      public boolean isEchoing() {
180        return echoing;
181      }
182    
183      public void setEchoing(boolean echoing) {
184        Console.this.echoing = echoing;
185      }
186    
187      /**
188       * Returns the console reader.
189       *
190       * @return the console reader
191       */
192      public ConsoleReader getReader() {
193        return reader;
194      }
195    
196      public ViewReader getViewReader() {
197        return viewReader;
198      }
199    
200      public ConsoleWriter getWriter() {
201        return writer;
202      }
203    
204      private void appendData(CharSequence s) throws IOException {
205        for (int i = 0;i < s.length();i++) {
206          appendData(s.charAt(i));
207        }
208      }
209    
210      private void appendData(char c) throws IOException {
211        if (previousCR && c == '\n') {
212          previousCR = false;
213        } else if (c == '\r' || c == '\n') {
214          previousCR = c == '\r';
215          String line = new String(buffer, 0, size);
216          lines.add(line);
217          size = 0;
218          curAt = size;
219          echoCRLF();
220        } else {
221          push(c);
222        }
223      }
224    
225      private int appendDel() throws IOException {
226        if (curAt == size){
227          int popped = pop();
228    
229          //
230          if (popped != -1) {
231    //        previous = buffer[size];
232            echoDel();
233          } else {
234    //        previous = -1;
235          }
236    
237          //
238          return popped;
239        } else {
240          // Cursor in body
241          if (curAt == 0) {
242    //        previous = -1;
243            return -1;
244          }
245          // remove previous char from buffer
246          for (int idx = curAt-1; idx < size; idx++) {
247            buffer[idx] = buffer[idx+1];
248          }
249          buffer[size-1] = ' ';
250          // Adjust cursor at and size
251          --size;
252          moveLeft();
253          // Redisplay from cursor to end
254          String disp = new String(buffer, curAt, size - curAt + 1);
255          viewWriter.write(disp);
256          // position cursor one to left from where started
257          int saveCurAt = curAt;
258          curAt = size + 1;   // Size before delete
259          while (curAt > saveCurAt) {
260            moveLeft();
261          }
262          if (curAt == 0) {
263            return -1;
264          }
265          return buffer[curAt - 1];
266        }
267      }
268    
269      private void moveRight() throws IOException {
270        if (curAt < size) {
271          if (viewWriter.writeMoveRight(buffer[curAt]))
272          {
273            viewWriter.flush();
274            curAt++;
275          }
276        }
277      }
278    
279      private void moveLeft() throws IOException {
280        if (curAt > 0) {
281          if (viewWriter.writeMoveLeft())
282          {
283            viewWriter.flush();
284            curAt--;
285          }
286        }
287      }
288    
289      private void echo(char c) throws IOException {
290        if (echoing) {
291          viewWriter.write(c);
292          viewWriter.flush();
293        }
294      }
295    
296      private void echo(String s) throws IOException {
297        if (echoing) {
298          viewWriter.write(s);
299          viewWriter.flush();
300        }
301      }
302    
303      private void echoDel() throws IOException {
304        if (echoing) {
305          viewWriter.writeDel();
306          viewWriter.flush();
307        }
308      }
309    
310      private void echoCRLF() throws IOException {
311        if (echoing) {
312          viewWriter.writeCRLF();
313          viewWriter.flush();
314        }
315      }
316    
317      private int pop() {
318        if (size > 0) {
319          --size;
320          curAt = size;
321          return buffer[size];
322        } else {
323          curAt = size;
324          return -1;
325        }
326      }
327    
328      private void push(char c) throws IOException {
329        if (size >= buffer.length) {
330          char[] tmp = new char[buffer.length * 2 + 1];
331          System.arraycopy(buffer, 0, tmp, 0, buffer.length);
332          Console.this.buffer = tmp;
333        }
334        if (curAt == size) {
335          buffer[size++] = c;
336          curAt = size;
337    //      previous = c;
338          echo(c);
339        } else {
340          // In body
341          // Shift buffer one from position before cursor
342          for (int idx = size-1; idx > curAt - 1; idx--) {
343            buffer[idx+1] = buffer[idx];
344          }
345          buffer[curAt] = c;
346          // Adjust size and display from inserted character to end
347          ++size;
348          String disp = new String(buffer, curAt, size - curAt);
349          viewWriter.write(disp);
350          // Move cursor to original character
351          int saveCurAt = ++curAt;
352          curAt = size;
353          while (curAt > saveCurAt) {
354            moveLeft();
355          }
356    //      previous = buffer[curAt-1];
357        }
358      }
359    }