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