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