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     */
017    package org.apache.logging.log4j.core.pattern;
018    
019    import java.util.Arrays;
020    import java.util.HashMap;
021    import java.util.Locale;
022    import 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 device used to
028     * display them.
029     * </p>
030     */
031    public enum AnsiEscape {
032    
033        PREFIX("\u001b["),
034        SUFFIX("m"),
035        SEPARATOR(";"),
036    
037        /**
038         * General Attributes.
039         */
040        NORMAL("0"),
041        BRIGHT("1"),
042        DIM("2"),
043        UNDERLINE("3"),
044        BLINK("5"),
045        REVERSE("7"),
046        HIDDEN("8"),
047    
048        /**
049         * Foreground Colors.
050         */
051        BLACK("30"),
052        FG_BLACK("30"),
053        RED("31"),
054        FG_RED("31"),
055        GREEN("32"),
056        FG_GREEN("32"),
057        YELLOW("33"),
058        FG_YELLOW("33"),
059        BLUE("34"),
060        FG_BLUE("34"),
061        MAGENTA("35"),
062        FG_MAGENTA("35"),
063        CYAN("36"),
064        FG_CYAN("36"),
065        WHITE("37"),
066        FG_WHITE("37"),
067        DEFAULT("39"),
068        FG_DEFAULT("39"),
069    
070        /**
071         * Background Colors.
072         */
073        BG_BLACK("40"),
074        BG_RED("41"),
075        BG_GREEN("42"),
076        BG_YELLOW("43"),
077        BG_BLUE("44"),
078        BG_MAGENTA("45"),
079        BG_CYAN("46"),
080        BG_WHITE("47");
081    
082        private static final String WHITESPACE_REGEX = "\\s*";
083        
084        private final String code;
085    
086        private AnsiEscape(String code) {
087            this.code = code;
088        }
089    
090        public static String getDefaultStyle() {
091            return PREFIX.getCode() + SUFFIX.getCode();
092        }
093    
094        private static String toRegexSeparator(String separator) {
095            return WHITESPACE_REGEX + separator + WHITESPACE_REGEX;
096        }
097    
098        public String getCode() {
099            return code;
100        }
101    
102        /**
103         * Creates a Map from a source array where values are ANSI escape sequences. The format is:
104         * 
105         * <pre>
106         * Key1=Value, Key2=Value, ...
107         * </pre>
108         * 
109         * For example:
110         * 
111         * <pre>
112         * ERROR=red bold, WARN=yellow bold, INFO=green, ...
113         * </pre>
114         * 
115         * You can use whitespace around the comma and equal sign. The names in values MUST come from the {@linkplain AnsiEscape} enum, case is
116         * normalized to upper-case internally.
117         * 
118         * @param values
119         *            the source string to parse.
120         * @param dontEscapeKeys
121         *            do not escape these keys, leave the values as is in the map
122         * @return a new map
123         */
124        public static Map<String, String> createMap(String values, String[] dontEscapeKeys) {
125            return createMap(values.split(toRegexSeparator(",")), dontEscapeKeys);
126        }
127    
128        /**
129         * Creates a Map from a source array where values are ANSI escape sequences. Each array entry must be in the format:
130         * 
131         * <pre>
132         * Key1 = Value
133         * </pre>
134         * 
135         * For example:
136         * 
137         * <pre>
138         * ERROR=red bold
139         * </pre>
140         * 
141         * You can use whitespace around the equal sign and between the value elements. The names in values MUST come from the
142         * {@linkplain AnsiEscape} enum, case is normalized to upper-case internally.
143         * 
144         * @param values
145         *            the source array to parse.
146         * @param dontEscapeKeys
147         *            do not escape these keys, leave the values as is in the map
148         * @return a new map
149         */
150        public static Map<String, String> createMap(String[] values, String[] dontEscapeKeys) {
151            final String[] sortedIgnoreKeys = dontEscapeKeys != null ? dontEscapeKeys.clone() : new String[0];
152            Arrays.sort(sortedIgnoreKeys);
153            Map<String, String> map = new HashMap<String, String>();
154            for (String string : values) {
155                String[] keyValue = string.split(toRegexSeparator("="));
156                if (keyValue.length > 1) {
157                    final String key = keyValue[0].toUpperCase(Locale.ENGLISH);
158                    final String value = keyValue[1];
159                    final boolean escape = Arrays.binarySearch(sortedIgnoreKeys, key) < 0;
160                    map.put(key, escape ? createSequence(value.split("\\s")) : value);
161                }
162            }
163            return map;
164        }
165    
166        /**
167         * Creates an ANSI escape sequence from the given {@linkplain AnsiEscape} names.
168         * 
169         * @param names
170         *            {@linkplain AnsiEscape} names.
171         * @return An ANSI escape sequence.
172         */
173        public static String createSequence(String[] names) {
174            if (names == null) {
175                return getDefaultStyle();
176            }
177            StringBuilder sb = new StringBuilder(AnsiEscape.PREFIX.getCode());
178            boolean first = true;
179            for (String name : names) {
180                try {
181                    AnsiEscape escape = AnsiEscape.valueOf(name.trim().toUpperCase(Locale.ENGLISH));
182                    if (!first) {
183                        sb.append(AnsiEscape.SEPARATOR.getCode());
184                    }
185                    first = false;
186                    sb.append(escape.getCode());
187                } catch (Exception ex) {
188                    // Ignore the error.
189                }
190            }
191            sb.append(AnsiEscape.SUFFIX.getCode());
192            return sb.toString();
193        }
194    
195    }