001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. 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 */
017package org.apache.logging.log4j.core.pattern;
018
019import java.util.Arrays;
020import java.util.HashMap;
021import java.util.Locale;
022import java.util.Map;
023
024import org.apache.logging.log4j.core.util.Patterns;
025import org.apache.logging.log4j.util.EnglishEnums;
026import org.apache.logging.log4j.util.Strings;
027
028/**
029 * Converts text into ANSI escape sequences.
030 * <p>
031 * The names for colors and attributes are standard, but the exact shade/hue/value of colors are not, and depend on the
032 * device used to display them.
033 * </p>
034 */
035public enum AnsiEscape {
036
037    /**
038     * The Control Sequence Introducer (or Control Sequence Initiator).
039     * <p>
040     * Most sequences are more than two characters and start with the characters ESC and [ (the left bracket).
041     * </p>
042     */
043    CSI("\u001b["),
044
045    /**
046     * Escape suffix.
047     */
048    SUFFIX("m"),
049
050    /**
051     * Escape separator.
052     */
053    SEPARATOR(";"),
054
055    /**
056     * Normal general attribute.
057     */
058    NORMAL("0"),
059
060    /**
061     * Bright general attribute.
062     */
063    BRIGHT("1"),
064
065    /**
066     * Dim general attribute.
067     */
068    DIM("2"),
069
070    /**
071     * Underline general attribute.
072     */
073    UNDERLINE("3"),
074
075    /**
076     * Blink general attribute.
077     */
078    BLINK("5"),
079
080    /**
081     * Reverse general attribute.
082     */
083    REVERSE("7"),
084
085    /**
086     * Normal general attribute.
087     */
088    HIDDEN("8"),
089
090    /**
091     * Black foreground color.
092     */
093    BLACK("30"),
094
095    /**
096     * Black foreground color.
097     */
098    FG_BLACK("30"),
099
100    /**
101     * Red foreground color.
102     */
103    RED("31"),
104
105    /**
106     * Red foreground color.
107     */
108    FG_RED("31"),
109
110    /**
111     * Green foreground color.
112     */
113    GREEN("32"),
114
115    /**
116     * Green foreground color.
117     */
118    FG_GREEN("32"),
119
120    /**
121     * Yellow foreground color.
122     */
123    YELLOW("33"),
124
125    /**
126     * Yellow foreground color.
127     */
128    FG_YELLOW("33"),
129
130    /**
131     * Blue foreground color.
132     */
133    BLUE("34"),
134
135    /**
136     * Blue foreground color.
137     */
138    FG_BLUE("34"),
139
140    /**
141     * Magenta foreground color.
142     */
143    MAGENTA("35"),
144
145    /**
146     * Magenta foreground color.
147     */
148    FG_MAGENTA("35"),
149
150    /**
151     * Cyan foreground color.
152     */
153    CYAN("36"),
154
155    /**
156     * Cyan foreground color.
157     */
158    FG_CYAN("36"),
159
160    /**
161     * White foreground color.
162     */
163    WHITE("37"),
164
165    /**
166     * White foreground color.
167     */
168    FG_WHITE("37"),
169
170    /**
171     * Default foreground color.
172     */
173    DEFAULT("39"),
174
175    /**
176     * Default foreground color.
177     */
178    FG_DEFAULT("39"),
179
180    /**
181     * Black background color.
182     */
183    BG_BLACK("40"),
184
185    /**
186     * Red background color.
187     */
188    BG_RED("41"),
189
190    /**
191     * Green background color.
192     */
193    BG_GREEN("42"),
194
195    /**
196     * Yellow background color.
197     */
198    BG_YELLOW("43"),
199
200    /**
201     * Blue background color.
202     */
203    BG_BLUE("44"),
204
205    /**
206     * Magenta background color.
207     */
208    BG_MAGENTA("45"),
209
210    /**
211     * Cyan background color.
212     */
213    BG_CYAN("46"),
214
215    /**
216     * White background color.
217     */
218    BG_WHITE("47");
219
220    private static final String DEFAULT_STYLE = CSI.getCode() + SUFFIX.getCode();
221
222    private final String code;
223
224    AnsiEscape(final String code) {
225        this.code = code;
226    }
227
228    /**
229     * Gets the default style.
230     *
231     * @return the default style
232     */
233    public static String getDefaultStyle() {
234        return DEFAULT_STYLE;
235    }
236
237    /**
238     * Gets the escape code.
239     *
240     * @return the escape code.
241     */
242    public String getCode() {
243        return code;
244    }
245
246    /**
247     * Creates a Map from a source array where values are ANSI escape sequences. The format is:
248     *
249     * <pre>
250     * Key1=Value, Key2=Value, ...
251     * </pre>
252     *
253     * For example:
254     *
255     * <pre>
256     * ERROR=red bold, WARN=yellow bold, INFO=green, ...
257     * </pre>
258     *
259     * You can use whitespace around the comma and equal sign. The names in values MUST come from the
260     * {@linkplain AnsiEscape} enum, case is normalized to upper-case internally.
261     *
262     * @param values the source string to parse.
263     * @param dontEscapeKeys do not escape these keys, leave the values as is in the map
264     * @return a new map
265     */
266    public static Map<String, String> createMap(final String values, final String[] dontEscapeKeys) {
267        return createMap(values.split(Patterns.COMMA_SEPARATOR), dontEscapeKeys);
268    }
269
270    /**
271     * Creates a Map from a source array where values are ANSI escape sequences. Each array entry must be in the format:
272     *
273     * <pre>
274     * Key1 = Value
275     * </pre>
276     *
277     * For example:
278     *
279     * <pre>
280     * ERROR=red bold
281     * </pre>
282     *
283     * You can use whitespace around the equal sign and between the value elements. The names in values MUST come from
284     * the {@linkplain AnsiEscape} enum, case is normalized to upper-case internally.
285     *
286     * @param values
287     *            the source array to parse.
288     * @param dontEscapeKeys
289     *            do not escape these keys, leave the values as is in the map
290     * @return a new map
291     */
292    public static Map<String, String> createMap(final String[] values, final String[] dontEscapeKeys) {
293        final String[] sortedIgnoreKeys = dontEscapeKeys != null ? dontEscapeKeys.clone() : Strings.EMPTY_ARRAY;
294        Arrays.sort(sortedIgnoreKeys);
295        final Map<String, String> map = new HashMap<>();
296        for (final String string : values) {
297            final String[] keyValue = string.split(Patterns.toWhitespaceSeparator("="));
298            if (keyValue.length > 1) {
299                final String key = keyValue[0].toUpperCase(Locale.ENGLISH);
300                final String value = keyValue[1];
301                final boolean escape = Arrays.binarySearch(sortedIgnoreKeys, key) < 0;
302                map.put(key, escape ? createSequence(value.split("\\s")) : value);
303            }
304        }
305        return map;
306    }
307
308    /**
309     * Creates an ANSI escape sequence from the given {@linkplain AnsiEscape} names.
310     *
311     * @param names
312     *            {@linkplain AnsiEscape} names.
313     * @return An ANSI escape sequence.
314     */
315    public static String createSequence(final String... names) {
316        if (names == null) {
317            return getDefaultStyle();
318        }
319        final StringBuilder sb = new StringBuilder(AnsiEscape.CSI.getCode());
320        boolean first = true;
321        for (final String name : names) {
322            try {
323                final AnsiEscape escape = EnglishEnums.valueOf(AnsiEscape.class, name.trim());
324                if (!first) {
325                    sb.append(AnsiEscape.SEPARATOR.getCode());
326                }
327                first = false;
328                sb.append(escape.getCode());
329            } catch (final Exception ex) {
330                // Ignore the error.
331            }
332        }
333        sb.append(AnsiEscape.SUFFIX.getCode());
334        return sb.toString();
335    }
336
337}