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}