001    /**
002     * Copyright (C) 2009, Progress Software Corporation and/or its 
003     * subsidiaries or affiliates.  All rights reserved.
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.fusesource.jansi;
018    
019    import java.util.ArrayList;
020    import java.util.concurrent.Callable;
021    
022    /**
023     * Provides a fluent API for generating ANSI escape sequences.
024     * 
025     * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
026     * @since 1.0
027     */
028    public class Ansi {
029    
030        private static final char FIRST_ESC_CHAR = 27;
031            private static final char SECOND_ESC_CHAR = '[';
032    
033            public static enum Color {
034                    BLACK(0, "BLACK"), 
035                    RED(1, "RED"), 
036                    GREEN(2, "GREEN"), 
037                    YELLOW(3, "YELLOW"), 
038                    BLUE(4, "BLUE"), 
039                    MAGENTA(5, "MAGENTA"), 
040                    CYAN(6, "CYAN"), 
041                    WHITE(7,"WHITE"),
042                    DEFAULT(9,"DEFAULT");
043    
044                    private final int value;
045                    private final String name;
046    
047                    Color(int index, String name) {
048                            this.value = index;
049                            this.name = name;
050                    }
051    
052                    @Override
053                    public String toString() {
054                            return name;
055                    }
056    
057                    public int value() {
058                            return value;
059                    }
060    
061                    public int fg() {
062                            return value + 30;
063                    }
064    
065                    public int bg() {
066                            return value + 40;
067                    }
068                    
069                    public int fgBright() {
070                            return value + 90;
071                    }
072                    
073                    public int bgBright() {
074                            return value + 100;
075                    }
076            };
077    
078            public static enum Attribute {
079                    RESET                                           (  0, "RESET"), 
080                    INTENSITY_BOLD                          (  1, "INTENSITY_BOLD"), 
081                    INTENSITY_FAINT                         (  2, "INTENSITY_FAINT"), 
082                    ITALIC                                          (  3, "ITALIC_ON"), 
083                    UNDERLINE                                       (  4, "UNDERLINE_ON"), 
084                    BLINK_SLOW                                      (  5, "BLINK_SLOW"), 
085                    BLINK_FAST                                      (  6, "BLINK_FAST"), 
086                    NEGATIVE_ON                                     (  7, "NEGATIVE_ON"), 
087                    CONCEAL_ON                                      (  8, "CONCEAL_ON"),
088                    STRIKETHROUGH_ON                        (  9, "STRIKETHROUGH_ON"),
089                    UNDERLINE_DOUBLE                        ( 21, "UNDERLINE_DOUBLE"), 
090                    INTENSITY_BOLD_OFF                      ( 22, "INTENSITY_BOLD_OFF"),
091                    ITALIC_OFF                                      ( 23, "ITALIC_OFF"),
092                    UNDERLINE_OFF                           ( 24, "UNDERLINE_OFF"), 
093                    BLINK_OFF                                       ( 25, "BLINK_OFF"), 
094                    NEGATIVE_OFF                            ( 27, "NEGATIVE_OFF"), 
095                    CONCEAL_OFF                                     ( 28, "CONCEAL_OFF"),
096                    STRIKETHROUGH_OFF                       ( 29, "STRIKETHROUGH_OFF");
097                    
098                    private final int value;
099                    private final String name;
100    
101                    Attribute(int index, String name) {
102                            this.value = index;
103                            this.name = name;
104                    }
105    
106                    @Override
107                    public String toString() {
108                            return name;
109                    }
110    
111                    public int value() {
112                            return value;
113                    }
114    
115            };
116    
117            public static enum Erase {
118                    FORWARD(0, "FORWARD"),
119                    BACKWARD(1, "BACKWARD"), 
120                    ALL(2, "ALL"); 
121    
122                    private final int value;
123                    private final String name;
124    
125                    Erase(int index, String name) {
126                            this.value = index;
127                            this.name = name;
128                    }
129    
130                    @Override
131                    public String toString() {
132                            return name;
133                    }
134    
135                    public int value() {
136                            return value;
137                    }
138            };
139    
140        public static final String DISABLE = Ansi.class.getName() + ".disable";
141    
142        private static Callable<Boolean> detector = new Callable<Boolean>() {
143            public Boolean call() throws Exception {
144                return !Boolean.getBoolean(DISABLE);
145            }
146        };
147    
148        public static void setDetector(final Callable<Boolean> detector) {
149            if (detector == null) throw new IllegalArgumentException();
150            Ansi.detector = detector;
151        }
152    
153        public static boolean isDetected() {
154            try {
155                return detector.call();
156            }
157            catch (Exception e) {
158                return true;
159            }
160        }
161    
162        private static final InheritableThreadLocal<Boolean> holder = new InheritableThreadLocal<Boolean>()
163        {
164            @Override
165            protected Boolean initialValue() {
166                return isDetected();
167            }
168        };
169    
170        public static void setEnabled(final boolean flag) {
171            holder.set(flag);
172        }
173    
174        public static boolean isEnabled() {
175            return holder.get();
176        }
177    
178        public static Ansi ansi() {
179            if (isEnabled()) {
180                return new Ansi();
181            }
182            else {
183                return new NoAnsi();
184            }
185        }
186    
187        private static class NoAnsi
188            extends Ansi
189        {
190            @Override
191            public Ansi fg(Color color) {
192                return this;
193            }
194    
195            @Override
196            public Ansi bg(Color color) {
197                return this;
198            }
199    
200            @Override
201            public Ansi a(Attribute attribute) {
202                return this;
203            }
204    
205            @Override
206            public Ansi cursor(int x, int y) {
207                return this;
208            }
209    
210            @Override
211            public Ansi cursorUp(int y) {
212                return this;
213            }
214    
215            @Override
216            public Ansi cursorRight(int x) {
217                return this;
218            }
219    
220            @Override
221            public Ansi cursorDown(int y) {
222                return this;
223            }
224    
225            @Override
226            public Ansi cursorLeft(int x) {
227                return this;
228            }
229    
230            @Override
231            public Ansi eraseScreen() {
232                return this;
233            }
234    
235            @Override
236            public Ansi eraseScreen(Erase kind) {
237                return this;
238            }
239    
240            @Override
241            public Ansi eraseLine() {
242                return this;
243            }
244    
245            @Override
246            public Ansi eraseLine(Erase kind) {
247                return this;
248            }
249    
250            @Override
251            public Ansi scrollUp(int rows) {
252                return this;
253            }
254    
255            @Override
256            public Ansi scrollDown(int rows) {
257                return this;
258            }
259    
260            @Override
261            public Ansi saveCursorPosition() {
262                return this;
263            }
264    
265            @Override
266            public Ansi restorCursorPosition() {
267                return this;
268            }
269    
270            @Override
271            public Ansi reset() {
272                return this;
273            }
274        }
275    
276            private final StringBuilder builder;
277            private final ArrayList<Integer> attributeOptions = new ArrayList<Integer>(5);
278            
279            public Ansi() {
280                    this(new StringBuilder());
281            }
282    
283        public Ansi(int size) {
284                    this(new StringBuilder(size));
285            }
286    
287        public Ansi(StringBuilder builder) {
288                    this.builder = builder;
289            }
290    
291            public static Ansi ansi(StringBuilder builder) {
292                    return new Ansi(builder);
293            }
294            public static Ansi ansi(int size) {
295                    return new Ansi(size);
296            }
297    
298            public Ansi fg(Color color) {
299                    attributeOptions.add(color.fg());
300                    return this;
301            }
302    
303            public Ansi bg(Color color) {
304                    attributeOptions.add(color.bg());
305                    return this;
306            }
307    
308            public Ansi a(Attribute attribute) {
309                    attributeOptions.add(attribute.value());
310                    return this;
311            }
312            
313            public Ansi cursor(final int x, final int y) {
314                    return appendEscapeSequence('H', x, y);
315            }
316    
317            public Ansi cursorUp(final int y) {
318                    return appendEscapeSequence('A', y);
319            }
320    
321            public Ansi cursorDown(final int y) {
322                    return appendEscapeSequence('B', y);
323            }
324    
325            public Ansi cursorRight(final int x) {
326                    return appendEscapeSequence('C', x);
327            }
328    
329            public Ansi cursorLeft(final int x) {
330                    return appendEscapeSequence('D', x);
331            }
332    
333            public Ansi eraseScreen() {
334                    return appendEscapeSequence('J',Erase.ALL.value());
335            }
336    
337            public Ansi eraseScreen(final Erase kind) {
338                    return appendEscapeSequence('J', kind.value());
339            }
340    
341            public Ansi eraseLine() {
342                    return appendEscapeSequence('K');
343            }
344    
345            public Ansi eraseLine(final Erase kind) {
346                    return appendEscapeSequence('K', kind.value());
347            }
348    
349            public Ansi scrollUp(final int rows) {
350                    return appendEscapeSequence('S', rows);
351            }
352    
353            public Ansi scrollDown(final int rows) {
354                    return appendEscapeSequence('T', rows);
355            }
356    
357            public Ansi saveCursorPosition() {
358                    return appendEscapeSequence('s');
359            }
360    
361            public Ansi restorCursorPosition() {
362                    return appendEscapeSequence('u');
363            }
364    
365            public Ansi reset() {
366                    return a(Attribute.RESET);
367            }
368    
369            public Ansi a(String value) {
370                    flushAtttributes();             
371                    builder.append(value);
372                    return this;
373            }
374    
375            public Ansi a(boolean value) {
376                    flushAtttributes();             
377                    builder.append(value);
378                    return this;
379            }
380    
381            public Ansi a(char value) {
382                    flushAtttributes();             
383                    builder.append(value);
384                    return this;
385            }
386    
387            public Ansi a(char[] value, int offset, int len) {
388                    flushAtttributes();             
389                    builder.append(value, offset, len);
390                    return this;
391            }
392    
393            public Ansi a(char[] value) {
394                    flushAtttributes();             
395                    builder.append(value);
396                    return this;
397            }
398    
399            public Ansi a(CharSequence value, int start, int end) {
400                    flushAtttributes();             
401                    builder.append(value, start, end);
402                    return this;
403            }
404    
405            public Ansi a(CharSequence value) {
406                    flushAtttributes();             
407                    builder.append(value);
408                    return this;
409            }
410    
411            public Ansi a(double value) {
412                    flushAtttributes();             
413                    builder.append(value);
414                    return this;
415            }
416    
417            public Ansi a(float value) {
418                    flushAtttributes();             
419                    builder.append(value);
420                    return this;
421            }
422    
423            public Ansi a(int value) {
424                    flushAtttributes();             
425                    builder.append(value);
426                    return this;
427            }
428    
429            public Ansi a(long value) {
430                    flushAtttributes();             
431                    builder.append(value);
432                    return this;
433            }
434    
435            public Ansi a(Object value) {
436                    flushAtttributes();             
437                    builder.append(value);
438                    return this;
439            }
440    
441            public Ansi a(StringBuffer value) {
442                    flushAtttributes();             
443                    builder.append(value);
444                    return this;
445            }
446    
447        public Ansi newline() {
448            flushAtttributes();
449                    builder.append(System.getProperty("line.separator"));
450                    return this;
451        }
452    
453        public Ansi format(String pattern, Object... args) {
454            flushAtttributes();
455            builder.append(String.format(pattern, args));
456            return this;
457        }
458    
459        /**
460         * Uses the {@link AnsiRenderer} 
461         * to generate the ANSI escape sequences for the supplied text.
462         * 
463         * @since 1.1
464         * @param text
465         */
466        public Ansi render(final String text) {
467            a(AnsiRenderer.render(text));
468            return this;
469        }
470    
471        /**
472         * String formats and renders the supplied arguments.  Uses the {@link AnsiRenderer} 
473         * to generate the ANSI escape sequences.
474         * 
475         * @since 1.1
476         * @param text
477         * @param args
478         */
479        public Ansi render(final String text, Object... args) {
480            a(String.format(AnsiRenderer.render(text), args));
481            return this;
482        }
483    
484            @Override
485            public String toString() {
486                    flushAtttributes();             
487                    return builder.toString();
488            }
489    
490            ///////////////////////////////////////////////////////////////////
491            // Private Helper Methods
492            ///////////////////////////////////////////////////////////////////
493            
494            private Ansi appendEscapeSequence(char command) {
495                    flushAtttributes();
496                    builder.append(FIRST_ESC_CHAR);
497                    builder.append(SECOND_ESC_CHAR);
498                    builder.append(command);
499                    return this;
500            }
501            
502            private Ansi appendEscapeSequence(char command, int option) {
503                    flushAtttributes();
504                    builder.append(FIRST_ESC_CHAR);
505                    builder.append(SECOND_ESC_CHAR);
506                    builder.append(option);
507                    builder.append(command);
508                    return this;
509            }
510            
511            private Ansi appendEscapeSequence(char command, Object... options) {
512                    flushAtttributes();
513                    return _appendEscapeSequence(command, options);
514            }
515    
516            private void flushAtttributes() {
517                    if( attributeOptions.isEmpty() )
518                            return;
519                    if (attributeOptions.size() == 1 && attributeOptions.get(0) == 0) {
520                            builder.append(FIRST_ESC_CHAR);
521                            builder.append(SECOND_ESC_CHAR);
522                            builder.append('m');
523                    } else {
524                            _appendEscapeSequence('m', attributeOptions.toArray());
525                    }
526                    attributeOptions.clear();
527            }
528            
529            private Ansi _appendEscapeSequence(char command, Object... options) {
530                    builder.append(FIRST_ESC_CHAR);
531                    builder.append(SECOND_ESC_CHAR);
532                    int size = options.length;
533                    for (int i = 0; i < size; i++) {
534                            if (i != 0) {
535                                    builder.append(';');
536                            }
537                            if (options[i] != null) {
538                                    builder.append(options[i]);
539                            }
540                    }
541                    builder.append(command);
542                    return this;
543            }
544            
545    }