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