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