001/*
002 * Copyright (C) 2009-2017 the original author(s).
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.fusesource.jansi;
017
018import java.io.IOException;
019import java.io.PrintStream; // expected diff with AnsiOutputStream.java
020import java.nio.charset.Charset;
021import java.util.ArrayList;
022import java.util.Iterator;
023
024/**
025 * A ANSI print stream extracts ANSI escape codes written to 
026 * a print stream and calls corresponding <code>process*</code> methods.
027 *
028 * <p>For more information about ANSI escape codes, see
029 * <a href="http://en.wikipedia.org/wiki/ANSI_escape_code">Wikipedia article</a>
030 *
031 * <p>This class just filters out the escape codes so that they are not
032 * sent out to the underlying OutputStream: <code>process*</code> methods
033 * are empty. Subclasses should actually perform the ANSI escape behaviors
034 * by implementing active code in <code>process*</code> methods.
035 *
036 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
037 * @author Joris Kuipers
038 * @since 1.7
039 * @see AnsiOutputStream
040 */
041public class AnsiPrintStream extends FilterPrintStream { // expected diff with AnsiOutputStream.java
042
043    public static final String RESET_CODE = "\033[0m"; // expected diff with AnsiOutputStream.java
044
045    public AnsiPrintStream(PrintStream ps) { // expected diff with AnsiOutputStream.java
046        super(ps); // expected diff with AnsiOutputStream.java
047    }
048
049    private final static int MAX_ESCAPE_SEQUENCE_LENGTH = 100;
050    private final byte[] buffer = new byte[MAX_ESCAPE_SEQUENCE_LENGTH];
051    private int pos = 0;
052    private int startOfValue;
053    private final ArrayList<Object> options = new ArrayList<Object>();
054
055    private static final int LOOKING_FOR_FIRST_ESC_CHAR = 0;
056    private static final int LOOKING_FOR_SECOND_ESC_CHAR = 1;
057    private static final int LOOKING_FOR_NEXT_ARG = 2;
058    private static final int LOOKING_FOR_STR_ARG_END = 3;
059    private static final int LOOKING_FOR_INT_ARG_END = 4;
060    private static final int LOOKING_FOR_OSC_COMMAND = 5;
061    private static final int LOOKING_FOR_OSC_COMMAND_END = 6;
062    private static final int LOOKING_FOR_OSC_PARAM = 7;
063    private static final int LOOKING_FOR_ST = 8;
064    private static final int LOOKING_FOR_CHARSET = 9;
065
066    int state = LOOKING_FOR_FIRST_ESC_CHAR;
067
068    private static final int FIRST_ESC_CHAR = 27;
069    private static final int SECOND_ESC_CHAR = '[';
070    private static final int SECOND_OSC_CHAR = ']';
071    private static final int BEL = 7;
072    private static final int SECOND_ST_CHAR = '\\';
073    private static final int SECOND_CHARSET0_CHAR = '(';
074    private static final int SECOND_CHARSET1_CHAR = ')';
075
076    @Override
077    protected synchronized boolean filter(int data) { // expected diff with AnsiOutputStream.java
078        switch (state) {
079            case LOOKING_FOR_FIRST_ESC_CHAR:
080                if (data == FIRST_ESC_CHAR) {
081                    buffer[pos++] = (byte) data;
082                    state = LOOKING_FOR_SECOND_ESC_CHAR;
083                    return false; // expected diff with AnsiOutputStream.java
084                }
085                return true; // expected diff with AnsiOutputStream.java
086
087            case LOOKING_FOR_SECOND_ESC_CHAR:
088                buffer[pos++] = (byte) data;
089                if (data == SECOND_ESC_CHAR) {
090                    state = LOOKING_FOR_NEXT_ARG;
091                } else if (data == SECOND_OSC_CHAR) {
092                    state = LOOKING_FOR_OSC_COMMAND;
093                } else if (data == SECOND_CHARSET0_CHAR) {
094                    options.add(Integer.valueOf(0));
095                    state = LOOKING_FOR_CHARSET;
096                } else if (data == SECOND_CHARSET1_CHAR) {
097                    options.add(Integer.valueOf(1));
098                    state = LOOKING_FOR_CHARSET;
099                } else {
100                    reset(false);
101                }
102                break;
103
104            case LOOKING_FOR_NEXT_ARG:
105                buffer[pos++] = (byte) data;
106                if ('"' == data) {
107                    startOfValue = pos - 1;
108                    state = LOOKING_FOR_STR_ARG_END;
109                } else if ('0' <= data && data <= '9') {
110                    startOfValue = pos - 1;
111                    state = LOOKING_FOR_INT_ARG_END;
112                } else if (';' == data) {
113                    options.add(null);
114                } else if ('?' == data) {
115                    options.add('?');
116                } else if ('=' == data) {
117                    options.add('=');
118                } else {
119                    reset(processEscapeCommand(options, data));
120                }
121                break;
122            default:
123                break;
124
125            case LOOKING_FOR_INT_ARG_END:
126                buffer[pos++] = (byte) data;
127                if (!('0' <= data && data <= '9')) {
128                    String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
129                    Integer value = new Integer(strValue);
130                    options.add(value);
131                    if (data == ';') {
132                        state = LOOKING_FOR_NEXT_ARG;
133                    } else {
134                        reset(processEscapeCommand(options, data));
135                    }
136                }
137                break;
138
139            case LOOKING_FOR_STR_ARG_END:
140                buffer[pos++] = (byte) data;
141                if ('"' != data) {
142                    String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
143                    options.add(value);
144                    if (data == ';') {
145                        state = LOOKING_FOR_NEXT_ARG;
146                    } else {
147                        reset(processEscapeCommand(options, data));
148                    }
149                }
150                break;
151
152            case LOOKING_FOR_OSC_COMMAND:
153                buffer[pos++] = (byte) data;
154                if ('0' <= data && data <= '9') {
155                    startOfValue = pos - 1;
156                    state = LOOKING_FOR_OSC_COMMAND_END;
157                } else {
158                    reset(false);
159                }
160                break;
161
162            case LOOKING_FOR_OSC_COMMAND_END:
163                buffer[pos++] = (byte) data;
164                if (';' == data) {
165                    String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
166                    Integer value = new Integer(strValue);
167                    options.add(value);
168                    startOfValue = pos;
169                    state = LOOKING_FOR_OSC_PARAM;
170                } else if ('0' <= data && data <= '9') {
171                    // already pushed digit to buffer, just keep looking
172                } else {
173                    // oops, did not expect this
174                    reset(false);
175                }
176                break;
177
178            case LOOKING_FOR_OSC_PARAM:
179                buffer[pos++] = (byte) data;
180                if (BEL == data) {
181                    String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
182                    options.add(value);
183                    reset(processOperatingSystemCommand(options));
184                } else if (FIRST_ESC_CHAR == data) {
185                    state = LOOKING_FOR_ST;
186                } else {
187                    // just keep looking while adding text
188                }
189                break;
190
191            case LOOKING_FOR_ST:
192                buffer[pos++] = (byte) data;
193                if (SECOND_ST_CHAR == data) {
194                    String value = new String(buffer, startOfValue, (pos - 2) - startOfValue, Charset.defaultCharset());
195                    options.add(value);
196                    reset(processOperatingSystemCommand(options));
197                } else {
198                    state = LOOKING_FOR_OSC_PARAM;
199                }
200                break;
201
202            case LOOKING_FOR_CHARSET:
203                options.add(Character.valueOf((char) data));
204                reset(processCharsetSelect(options));
205                break;
206        }
207
208        // Is it just too long?
209        if (pos >= buffer.length) {
210            reset(false);
211        }
212        return false; // expected diff with AnsiOutputStream.java
213    }
214
215    /**
216     * Resets all state to continue with regular parsing
217     * @param skipBuffer if current buffer should be skipped or written to out
218     */
219    private void reset(boolean skipBuffer) { // expected diff with AnsiOutputStream.java
220        if (!skipBuffer) {
221            ps.write(buffer, 0, pos); // expected diff with AnsiOutputStream.java
222        }
223        pos = 0;
224        startOfValue = 0;
225        options.clear();
226        state = LOOKING_FOR_FIRST_ESC_CHAR;
227    }
228
229    /**
230     * Helper for processEscapeCommand() to iterate over integer options
231     * @param  optionsIterator  the underlying iterator
232     * @throws IOException      if no more non-null values left
233     */
234    private int getNextOptionInt(Iterator<Object> optionsIterator) throws IOException {
235        for (;;) {
236            if (!optionsIterator.hasNext())
237                throw new IllegalArgumentException();
238            Object arg = optionsIterator.next();
239            if (arg != null)
240                return (Integer) arg;
241        }
242    }
243
244    /**
245     *
246     * @param options
247     * @param command
248     * @return true if the escape command was processed.
249     */
250    private boolean processEscapeCommand(ArrayList<Object> options, int command) { // expected diff with AnsiOutputStream.java
251        try {
252            switch (command) {
253                case 'A':
254                    processCursorUp(optionInt(options, 0, 1));
255                    return true;
256                case 'B':
257                    processCursorDown(optionInt(options, 0, 1));
258                    return true;
259                case 'C':
260                    processCursorRight(optionInt(options, 0, 1));
261                    return true;
262                case 'D':
263                    processCursorLeft(optionInt(options, 0, 1));
264                    return true;
265                case 'E':
266                    processCursorDownLine(optionInt(options, 0, 1));
267                    return true;
268                case 'F':
269                    processCursorUpLine(optionInt(options, 0, 1));
270                    return true;
271                case 'G':
272                    processCursorToColumn(optionInt(options, 0));
273                    return true;
274                case 'H':
275                case 'f':
276                    processCursorTo(optionInt(options, 0, 1), optionInt(options, 1, 1));
277                    return true;
278                case 'J':
279                    processEraseScreen(optionInt(options, 0, 0));
280                    return true;
281                case 'K':
282                    processEraseLine(optionInt(options, 0, 0));
283                    return true;
284                case 'L':
285                    processInsertLine(optionInt(options, 0, 1));
286                    return true;
287                case 'M':
288                    processDeleteLine(optionInt(options, 0, 1));
289                    return true;
290                case 'S':
291                    processScrollUp(optionInt(options, 0, 1));
292                    return true;
293                case 'T':
294                    processScrollDown(optionInt(options, 0, 1));
295                    return true;
296                case 'm':
297                    // Validate all options are ints...
298                    for (Object next : options) {
299                        if (next != null && next.getClass() != Integer.class) {
300                            throw new IllegalArgumentException();
301                        }
302                    }
303
304                    int count = 0;
305                    Iterator<Object> optionsIterator = options.iterator();
306                    while (optionsIterator.hasNext()) {
307                        Object next = optionsIterator.next();
308                        if (next != null) {
309                            count++;
310                            int value = (Integer) next;
311                            if (30 <= value && value <= 37) {
312                                processSetForegroundColor(value - 30);
313                            } else if (40 <= value && value <= 47) {
314                                processSetBackgroundColor(value - 40);
315                            } else if (90 <= value && value <= 97) {
316                                processSetForegroundColor(value - 90, true);
317                            } else if (100 <= value && value <= 107) {
318                                processSetBackgroundColor(value - 100, true);
319                            } else if (value == 38 || value == 48) {
320                                // extended color like `esc[38;5;<index>m` or `esc[38;2;<r>;<g>;<b>m`
321                                int arg2or5 = getNextOptionInt(optionsIterator);
322                                if (arg2or5 == 2) {
323                                    // 24 bit color style like `esc[38;2;<r>;<g>;<b>m`
324                                    int r = getNextOptionInt(optionsIterator);
325                                    int g = getNextOptionInt(optionsIterator);
326                                    int b = getNextOptionInt(optionsIterator);
327                                    if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
328                                        if (value == 38)
329                                            processSetForegroundColorExt(r, g, b);
330                                        else
331                                            processSetBackgroundColorExt(r, g, b);
332                                    } else {
333                                        throw new IllegalArgumentException();
334                                    }
335                                }
336                                else if (arg2or5 == 5) {
337                                    // 256 color style like `esc[38;5;<index>m`
338                                    int paletteIndex = getNextOptionInt(optionsIterator);
339                                    if (paletteIndex >= 0 && paletteIndex <= 255) {
340                                        if (value == 38)
341                                            processSetForegroundColorExt(paletteIndex);
342                                        else
343                                            processSetBackgroundColorExt(paletteIndex);
344                                    } else {
345                                        throw new IllegalArgumentException();
346                                    }
347                                }
348                                else {
349                                    throw new IllegalArgumentException();
350                                }
351                            } else {
352                                switch (value) {
353                                    case 39:
354                                        processDefaultTextColor();
355                                        break;
356                                    case 49:
357                                        processDefaultBackgroundColor();
358                                        break;
359                                    case 0:
360                                        processAttributeRest();
361                                        break;
362                                    default:
363                                        processSetAttribute(value);
364                                }
365                            }
366                        }
367                    }
368                    if (count == 0) {
369                        processAttributeRest();
370                    }
371                    return true;
372                case 's':
373                    processSaveCursorPosition();
374                    return true;
375                case 'u':
376                    processRestoreCursorPosition();
377                    return true;
378
379                default:
380                    if ('a' <= command && 'z' <= command) {
381                        processUnknownExtension(options, command);
382                        return true;
383                    }
384                    if ('A' <= command && 'Z' <= command) {
385                        processUnknownExtension(options, command);
386                        return true;
387                    }
388                    return false;
389            }
390        } catch (IllegalArgumentException ignore) {
391        } catch (IOException ioe) { // expected diff with AnsiOutputStream.java
392            setError(); // expected diff with AnsiOutputStream.java
393        }
394        return false;
395    }
396
397    /**
398     *
399     * @param options
400     * @return true if the operating system command was processed.
401     */
402    private boolean processOperatingSystemCommand(ArrayList<Object> options) { // expected diff with AnsiOutputStream.java
403        int command = optionInt(options, 0);
404        String label = (String) options.get(1);
405        // for command > 2 label could be composed (i.e. contain ';'), but we'll leave
406        // it to processUnknownOperatingSystemCommand implementations to handle that
407        try {
408            switch (command) {
409                case 0:
410                    processChangeIconNameAndWindowTitle(label);
411                    return true;
412                case 1:
413                    processChangeIconName(label);
414                    return true;
415                case 2:
416                    processChangeWindowTitle(label);
417                    return true;
418
419                default:
420                    // not exactly unknown, but not supported through dedicated process methods:
421                    processUnknownOperatingSystemCommand(command, label);
422                    return true;
423            }
424        } catch (IllegalArgumentException ignore) {
425        }
426        return false;
427    }
428
429    /**
430     * Process <code>CSI u</code> ANSI code, corresponding to <code>RCP – Restore Cursor Position</code>
431     * @throws IOException IOException
432     */
433    protected void processRestoreCursorPosition() throws IOException {
434    }
435
436    /**
437     * Process <code>CSI s</code> ANSI code, corresponding to <code>SCP – Save Cursor Position</code>
438     * @throws IOException IOException
439     */
440    protected void processSaveCursorPosition() throws IOException {
441    }
442
443    /**
444     * Process <code>CSI L</code> ANSI code, corresponding to <code>IL – Insert Line</code>
445     * @param optionInt option
446     * @throws IOException IOException
447     * @since 1.16
448     */
449    protected void processInsertLine(int optionInt) throws IOException {
450    }
451
452    /**
453     * Process <code>CSI M</code> ANSI code, corresponding to <code>DL – Delete Line</code>
454     * @param optionInt option
455     * @throws IOException IOException
456     * @since 1.16
457     */
458    protected void processDeleteLine(int optionInt) throws IOException {
459    }
460
461    /**
462     * Process <code>CSI n T</code> ANSI code, corresponding to <code>SD – Scroll Down</code>
463     * @param optionInt option
464     * @throws IOException IOException
465     */
466    protected void processScrollDown(int optionInt) throws IOException {
467    }
468
469    /**
470     * Process <code>CSI n U</code> ANSI code, corresponding to <code>SU – Scroll Up</code>
471     * @param optionInt option
472     * @throws IOException IOException
473     */
474    protected void processScrollUp(int optionInt) throws IOException {
475    }
476
477    protected static final int ERASE_SCREEN_TO_END = 0;
478    protected static final int ERASE_SCREEN_TO_BEGINING = 1;
479    protected static final int ERASE_SCREEN = 2;
480
481    /**
482     * Process <code>CSI n J</code> ANSI code, corresponding to <code>ED – Erase in Display</code>
483     * @param eraseOption eraseOption
484     * @throws IOException IOException
485     */
486    protected void processEraseScreen(int eraseOption) throws IOException {
487    }
488
489    protected static final int ERASE_LINE_TO_END = 0;
490    protected static final int ERASE_LINE_TO_BEGINING = 1;
491    protected static final int ERASE_LINE = 2;
492
493    /**
494     * Process <code>CSI n K</code> ANSI code, corresponding to <code>ED – Erase in Line</code>
495     * @param eraseOption eraseOption
496     * @throws IOException IOException
497     */
498    protected void processEraseLine(int eraseOption) throws IOException {
499    }
500
501    protected static final int ATTRIBUTE_INTENSITY_BOLD = 1; //         Intensity: Bold
502    protected static final int ATTRIBUTE_INTENSITY_FAINT = 2; //        Intensity; Faint        not widely supported
503    protected static final int ATTRIBUTE_ITALIC = 3; //         Italic; on      not widely supported. Sometimes treated as inverse.
504    protected static final int ATTRIBUTE_UNDERLINE = 4; //      Underline; Single
505    protected static final int ATTRIBUTE_BLINK_SLOW = 5; //     Blink; Slow     less than 150 per minute
506    protected static final int ATTRIBUTE_BLINK_FAST = 6; //     Blink; Rapid    MS-DOS ANSI.SYS; 150 per minute or more
507    protected static final int ATTRIBUTE_NEGATIVE_ON = 7; //    Image; Negative         inverse or reverse; swap foreground and background
508    protected static final int ATTRIBUTE_CONCEAL_ON = 8; //     Conceal on
509    protected static final int ATTRIBUTE_UNDERLINE_DOUBLE = 21; //      Underline; Double       not widely supported
510    protected static final int ATTRIBUTE_INTENSITY_NORMAL = 22; //      Intensity; Normal       not bold and not faint
511    protected static final int ATTRIBUTE_UNDERLINE_OFF = 24; //         Underline; None
512    protected static final int ATTRIBUTE_BLINK_OFF = 25; //     Blink; off
513    @Deprecated
514    protected static final int ATTRIBUTE_NEGATIVE_Off = 27; //  Image; Positive
515    protected static final int ATTRIBUTE_NEGATIVE_OFF = 27; //  Image; Positive
516    protected static final int ATTRIBUTE_CONCEAL_OFF = 28; //   Reveal  conceal off
517
518    /**
519     * process <code>SGR</code> other than <code>0</code> (reset), <code>30-39</code> (foreground),
520     * <code>40-49</code> (background), <code>90-97</code> (foreground high intensity) or
521     * <code>100-107</code> (background high intensity)
522     * @param attribute attribute
523     * @throws IOException IOException
524     * @see #processAttributeRest()
525     * @see #processSetForegroundColor(int)
526     * @see #processSetForegroundColor(int, boolean)
527     * @see #processSetForegroundColorExt(int)
528     * @see #processSetForegroundColorExt(int, int, int)
529     * @see #processDefaultTextColor()
530     * @see #processDefaultBackgroundColor()
531     */
532    protected void processSetAttribute(int attribute) throws IOException {
533    }
534
535    protected static final int BLACK = 0;
536    protected static final int RED = 1;
537    protected static final int GREEN = 2;
538    protected static final int YELLOW = 3;
539    protected static final int BLUE = 4;
540    protected static final int MAGENTA = 5;
541    protected static final int CYAN = 6;
542    protected static final int WHITE = 7;
543
544    /**
545     * process <code>SGR 30-37</code> corresponding to <code>Set text color (foreground)</code>.
546     * @param color the text color
547     * @throws IOException IOException
548     */
549    protected void processSetForegroundColor(int color) throws IOException {
550        processSetForegroundColor(color, false);
551    }
552
553    /**
554     * process <code>SGR 30-37</code> or <code>SGR 90-97</code> corresponding to
555     * <code>Set text color (foreground)</code> either in normal mode or high intensity.
556     * @param color the text color
557     * @param bright is high intensity?
558     * @throws IOException IOException
559     */
560    protected void processSetForegroundColor(int color, boolean bright) throws IOException {
561    }
562
563    /**
564     * process <code>SGR 38</code> corresponding to <code>extended set text color (foreground)</code>
565     * with a palette of 255 colors.
566     * @param paletteIndex the text color in the palette
567     * @throws IOException IOException
568     */
569    protected void processSetForegroundColorExt(int paletteIndex) throws IOException {
570    }
571
572    /**
573     * process <code>SGR 38</code> corresponding to <code>extended set text color (foreground)</code>
574     * with a 24 bits RGB definition of the color.
575     * @param r red
576     * @param g green
577     * @param b blue
578     * @throws IOException IOException
579     */
580    protected void processSetForegroundColorExt(int r, int g, int b) throws IOException {
581    }
582
583    /**
584     * process <code>SGR 40-47</code> corresponding to <code>Set background color</code>.
585     * @param color the background color
586     * @throws IOException IOException
587     */
588    protected void processSetBackgroundColor(int color) throws IOException {
589        processSetBackgroundColor(color, false);
590    }
591
592    /**
593     * process <code>SGR 40-47</code> or <code>SGR 100-107</code> corresponding to
594     * <code>Set background color</code> either in normal mode or high intensity.
595     * @param color the background color
596     * @param bright is high intensity?
597     * @throws IOException IOException
598     */
599    protected void processSetBackgroundColor(int color, boolean bright) throws IOException {
600    }
601
602    /**
603     * process <code>SGR 48</code> corresponding to <code>extended set background color</code>
604     * with a palette of 255 colors.
605     * @param paletteIndex the background color in the palette
606     * @throws IOException IOException
607     */
608    protected void processSetBackgroundColorExt(int paletteIndex) throws IOException {
609    }
610
611    /**
612     * process <code>SGR 48</code> corresponding to <code>extended set background color</code>
613     * with a 24 bits RGB definition of the color.
614     * @param r red
615     * @param g green
616     * @param b blue
617     * @throws IOException IOException
618     */
619    protected void processSetBackgroundColorExt(int r, int g, int b) throws IOException {
620    }
621
622    /**
623     * process <code>SGR 39</code> corresponding to <code>Default text color (foreground)</code>
624     * @throws IOException IOException
625     */
626    protected void processDefaultTextColor() throws IOException {
627    }
628
629    /**
630     * process <code>SGR 49</code> corresponding to <code>Default background color</code>
631     * @throws IOException IOException
632     */
633    protected void processDefaultBackgroundColor() throws IOException {
634    }
635
636    /**
637     * process <code>SGR 0</code> corresponding to <code>Reset / Normal</code>
638     * @throws IOException IOException
639     */
640    protected void processAttributeRest() throws IOException {
641    }
642
643    /**
644     * process <code>CSI n ; m H</code> corresponding to <code>CUP – Cursor Position</code> or
645     * <code>CSI n ; m f</code> corresponding to <code>HVP – Horizontal and Vertical Position</code>
646     * @param row row
647     * @param col col
648     * @throws IOException IOException
649     */
650    protected void processCursorTo(int row, int col) throws IOException {
651    }
652
653    /**
654     * process <code>CSI n G</code> corresponding to <code>CHA – Cursor Horizontal Absolute</code>
655     * @param x the column
656     * @throws IOException IOException
657     */
658    protected void processCursorToColumn(int x) throws IOException {
659    }
660
661    /**
662     * process <code>CSI n F</code> corresponding to <code>CPL – Cursor Previous Line</code>
663     * @param count line count
664     * @throws IOException IOException
665     */
666    protected void processCursorUpLine(int count) throws IOException {
667    }
668
669    /**
670     * process <code>CSI n E</code> corresponding to <code>CNL – Cursor Next Line</code>
671     * @param count line count
672     * @throws IOException IOException
673     */
674    protected void processCursorDownLine(int count) throws IOException {
675        // Poor mans impl..
676        for (int i = 0; i < count; i++) {
677            ps.write('\n'); // expected diff with AnsiOutputStream.java
678        }
679    }
680
681    /**
682     * process <code>CSI n D</code> corresponding to <code>CUB – Cursor Back</code>
683     * @param count count
684     * @throws IOException IOException
685     */
686    protected void processCursorLeft(int count) throws IOException {
687    }
688
689    /**
690     * process <code>CSI n C</code> corresponding to <code>CUF – Cursor Forward</code>
691     * @param count count
692     * @throws IOException IOException
693     */
694    protected void processCursorRight(int count) throws IOException {
695        // Poor mans impl..
696        for (int i = 0; i < count; i++) {
697            ps.write(' '); // expected diff with AnsiOutputStream.java
698        }
699    }
700
701    /**
702     * process <code>CSI n B</code> corresponding to <code>CUD – Cursor Down</code>
703     * @param count count
704     * @throws IOException IOException
705     */
706    protected void processCursorDown(int count) throws IOException {
707    }
708
709    /**
710     * process <code>CSI n A</code> corresponding to <code>CUU – Cursor Up</code>
711     * @param count count
712     * @throws IOException IOException
713     */
714    protected void processCursorUp(int count) throws IOException {
715    }
716
717    /**
718     * Process Unknown Extension
719     * @param options options
720     * @param command command
721     */
722    protected void processUnknownExtension(ArrayList<Object> options, int command) {
723    }
724
725    /**
726     * process <code>OSC 0;text BEL</code> corresponding to <code>Change Window and Icon label</code>
727     * @param label window title name
728     */
729    protected void processChangeIconNameAndWindowTitle(String label) {
730        processChangeIconName(label);
731        processChangeWindowTitle(label);
732    }
733
734    /**
735     * process <code>OSC 1;text BEL</code> corresponding to <code>Change Icon label</code>
736     * @param label icon label
737     */
738    protected void processChangeIconName(String label) {
739    }
740
741    /**
742     * process <code>OSC 2;text BEL</code> corresponding to <code>Change Window title</code>
743     * @param label window title text
744     */
745    protected void processChangeWindowTitle(String label) {
746    }
747
748    /**
749     * Process unknown <code>OSC</code> command.
750     * @param command command
751     * @param param param
752     */
753    protected void processUnknownOperatingSystemCommand(int command, String param) {
754    }
755
756    /**
757     * Process character set sequence.
758     * @param options options
759     * @return true if the charcter set select command was processed.
760     */
761    private boolean processCharsetSelect(ArrayList<Object> options) {
762        int set = optionInt(options, 0);
763        char seq = ((Character) options.get(1)).charValue();
764        processCharsetSelect(set, seq);
765        return true;
766    }
767
768    protected void processCharsetSelect(int set, char seq) {
769    }
770
771    private int optionInt(ArrayList<Object> options, int index) {
772        if (options.size() <= index)
773            throw new IllegalArgumentException();
774        Object value = options.get(index);
775        if (value == null)
776            throw new IllegalArgumentException();
777        if (!value.getClass().equals(Integer.class))
778            throw new IllegalArgumentException();
779        return (Integer) value;
780    }
781
782    private int optionInt(ArrayList<Object> options, int index, int defaultValue) {
783        if (options.size() > index) {
784            Object value = options.get(index);
785            if (value == null) {
786                return defaultValue;
787            }
788            return (Integer) value;
789        }
790        return defaultValue;
791    }
792
793    @Override
794    public void close() { // expected diff with AnsiOutputStream.java
795        print(RESET_CODE); // expected diff with AnsiOutputStream.java
796        flush();
797        super.close();
798    }
799
800}