001/*
002 * Copyright 2012-2018 the original author or authors.
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 */
016
017package org.springframework.boot.ansi;
018
019import java.util.Locale;
020
021import org.springframework.util.Assert;
022
023/**
024 * Generates ANSI encoded output, automatically attempting to detect if the terminal
025 * supports ANSI.
026 *
027 * @author Phillip Webb
028 */
029public abstract class AnsiOutput {
030
031        private static final String ENCODE_JOIN = ";";
032
033        private static Enabled enabled = Enabled.DETECT;
034
035        private static Boolean consoleAvailable;
036
037        private static Boolean ansiCapable;
038
039        private static final String OPERATING_SYSTEM_NAME = System.getProperty("os.name")
040                        .toLowerCase(Locale.ENGLISH);
041
042        private static final String ENCODE_START = "\033[";
043
044        private static final String ENCODE_END = "m";
045
046        private static final String RESET = "0;" + AnsiColor.DEFAULT;
047
048        /**
049         * Sets if ANSI output is enabled.
050         * @param enabled if ANSI is enabled, disabled or detected
051         */
052        public static void setEnabled(Enabled enabled) {
053                Assert.notNull(enabled, "Enabled must not be null");
054                AnsiOutput.enabled = enabled;
055        }
056
057        /**
058         * Sets if the System.console() is known to be available.
059         * @param consoleAvailable if the console is known to be available or {@code null} to
060         * use standard detection logic.
061         */
062        public static void setConsoleAvailable(Boolean consoleAvailable) {
063                AnsiOutput.consoleAvailable = consoleAvailable;
064        }
065
066        static Enabled getEnabled() {
067                return AnsiOutput.enabled;
068        }
069
070        /**
071         * Encode a single {@link AnsiElement} if output is enabled.
072         * @param element the element to encode
073         * @return the encoded element or an empty string
074         */
075        public static String encode(AnsiElement element) {
076                if (isEnabled()) {
077                        return ENCODE_START + element + ENCODE_END;
078                }
079                return "";
080        }
081
082        /**
083         * Create a new ANSI string from the specified elements. Any {@link AnsiElement}s will
084         * be encoded as required.
085         * @param elements the elements to encode
086         * @return a string of the encoded elements
087         */
088        public static String toString(Object... elements) {
089                StringBuilder sb = new StringBuilder();
090                if (isEnabled()) {
091                        buildEnabled(sb, elements);
092                }
093                else {
094                        buildDisabled(sb, elements);
095                }
096                return sb.toString();
097        }
098
099        private static void buildEnabled(StringBuilder sb, Object[] elements) {
100                boolean writingAnsi = false;
101                boolean containsEncoding = false;
102                for (Object element : elements) {
103                        if (element instanceof AnsiElement) {
104                                containsEncoding = true;
105                                if (!writingAnsi) {
106                                        sb.append(ENCODE_START);
107                                        writingAnsi = true;
108                                }
109                                else {
110                                        sb.append(ENCODE_JOIN);
111                                }
112                        }
113                        else {
114                                if (writingAnsi) {
115                                        sb.append(ENCODE_END);
116                                        writingAnsi = false;
117                                }
118                        }
119                        sb.append(element);
120                }
121                if (containsEncoding) {
122                        sb.append(writingAnsi ? ENCODE_JOIN : ENCODE_START);
123                        sb.append(RESET);
124                        sb.append(ENCODE_END);
125                }
126        }
127
128        private static void buildDisabled(StringBuilder sb, Object[] elements) {
129                for (Object element : elements) {
130                        if (!(element instanceof AnsiElement) && element != null) {
131                                sb.append(element);
132                        }
133                }
134        }
135
136        private static boolean isEnabled() {
137                if (enabled == Enabled.DETECT) {
138                        if (ansiCapable == null) {
139                                ansiCapable = detectIfAnsiCapable();
140                        }
141                        return ansiCapable;
142                }
143                return enabled == Enabled.ALWAYS;
144        }
145
146        private static boolean detectIfAnsiCapable() {
147                try {
148                        if (Boolean.FALSE.equals(consoleAvailable)) {
149                                return false;
150                        }
151                        if ((consoleAvailable == null) && (System.console() == null)) {
152                                return false;
153                        }
154                        return !(OPERATING_SYSTEM_NAME.contains("win"));
155                }
156                catch (Throwable ex) {
157                        return false;
158                }
159        }
160
161        /**
162         * Possible values to pass to {@link AnsiOutput#setEnabled}. Determines when to output
163         * ANSI escape sequences for coloring application output.
164         */
165        public enum Enabled {
166
167                /**
168                 * Try to detect whether ANSI coloring capabilities are available. The default
169                 * value for {@link AnsiOutput}.
170                 */
171                DETECT,
172
173                /**
174                 * Enable ANSI-colored output.
175                 */
176                ALWAYS,
177
178                /**
179                 * Disable ANSI-colored output.
180                 */
181                NEVER
182
183        }
184
185}