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.util;
018    
019    import java.util.Locale;
020    import java.util.Properties;
021    
022    import org.apache.logging.log4j.Logger;
023    import org.apache.logging.log4j.status.StatusLogger;
024    import org.apache.logging.log4j.util.PropertiesUtil;
025    
026    /**
027     * A convenience class to convert property values to specific types.
028     */
029    public final class OptionConverter {
030    
031        private static final Logger LOGGER = StatusLogger.getLogger();
032    
033        private static final String DELIM_START = "${";
034        private static final char DELIM_STOP = '}';
035        private static final int DELIM_START_LEN = 2;
036        private static final int DELIM_STOP_LEN = 1;
037        private static final int ONE_K = 1024;
038    
039        /**
040         * OptionConverter is a static class.
041         */
042        private OptionConverter() {
043        }
044    
045        public static String[] concatenateArrays(final String[] l, final String[] r) {
046            final int len = l.length + r.length;
047            final String[] a = new String[len];
048    
049            System.arraycopy(l, 0, a, 0, l.length);
050            System.arraycopy(r, 0, a, l.length, r.length);
051    
052            return a;
053        }
054    
055        public static String convertSpecialChars(final String s) {
056            char c;
057            final int len = s.length();
058            final StringBuilder sbuf = new StringBuilder(len);
059    
060            int i = 0;
061            while (i < len) {
062                c = s.charAt(i++);
063                if (c == '\\') {
064                    c = s.charAt(i++);
065                    switch (c) {
066                    case 'n':
067                        c = '\n';
068                        break;
069                    case 'r':
070                        c = '\r';
071                        break;
072                    case 't':
073                        c = '\t';
074                        break;
075                    case 'f':
076                        c = '\f';
077                        break;
078                    case 'b':
079                        c = '\b';
080                        break;
081                    case '"':
082                        c = '\"';
083                        break;
084                    case '\'':
085                        c = '\'';
086                        break;
087                    case '\\':
088                        c = '\\';
089                        break;
090                    default:
091                        // there is no default case.
092                    }
093                }
094                sbuf.append(c);
095            }
096            return sbuf.toString();
097        }
098    
099        public static Object instantiateByKey(final Properties props, final String key, final Class<?> superClass,
100                                       final Object defaultValue) {
101    
102            // Get the value of the property in string form
103            final String className = findAndSubst(key, props);
104            if (className == null) {
105                LOGGER.error("Could not find value for key {}", key);
106                return defaultValue;
107            }
108            // Trim className to avoid trailing spaces that cause problems.
109            return OptionConverter.instantiateByClassName(className.trim(), superClass,
110                defaultValue);
111        }
112    
113        /**
114         * If <code>value</code> is "true", then {@code true} is
115         * returned. If <code>value</code> is "false", then
116         * {@code false} is returned. Otherwise, <code>default</code> is
117         * returned.
118         *
119         * <p>Case of value is unimportant.</p>
120         * @param value The value to convert.
121         * @param defaultValue The default value.
122         * @return true or false, depending on the value and/or default.
123         */
124        public static boolean toBoolean(final String value, final boolean defaultValue) {
125            if (value == null) {
126                return defaultValue;
127            }
128            final String trimmedVal = value.trim();
129            if ("true".equalsIgnoreCase(trimmedVal)) {
130                return true;
131            }
132            if ("false".equalsIgnoreCase(trimmedVal)) {
133                return false;
134            }
135            return defaultValue;
136        }
137    
138        /**
139         * Convert the String value to an int.
140         * @param value The value as a String.
141         * @param defaultValue The default value.
142         * @return The value as an int.
143         */
144        public static int toInt(final String value, final int defaultValue) {
145            if (value != null) {
146                final String s = value.trim();
147                try {
148                    return Integer.parseInt(s);
149                } catch (final NumberFormatException e) {
150                    LOGGER.error("[{}] is not in proper int form.", s, e);
151                }
152            }
153            return defaultValue;
154        }
155    
156        /**
157         *
158         * @param value The size of the file as a String.
159         * @param defaultValue The default value.
160         * @return The size of the file as a long.
161         */
162        public static long toFileSize(final String value, final long defaultValue) {
163            if (value == null) {
164                return defaultValue;
165            }
166    
167            String str = value.trim().toUpperCase(Locale.ENGLISH);
168            long multiplier = 1;
169            int index;
170    
171            if ((index = str.indexOf("KB")) != -1) {
172                multiplier = ONE_K;
173                str = str.substring(0, index);
174            } else if ((index = str.indexOf("MB")) != -1) {
175                multiplier = ONE_K * ONE_K;
176                str = str.substring(0, index);
177            } else if ((index = str.indexOf("GB")) != -1) {
178                multiplier = ONE_K * ONE_K * ONE_K;
179                str = str.substring(0, index);
180            }
181            try {
182                return Long.parseLong(str) * multiplier;
183            } catch (final NumberFormatException e) {
184                LOGGER.error("[{}] is not in proper int form.", str);
185                LOGGER.error("[{}] not in expected format.", value, e);
186            }
187            return defaultValue;
188        }
189    
190        /**
191         * Find the value corresponding to <code>key</code> in
192         * <code>props</code>. Then perform variable substitution on the
193         * found value.
194         * @param key The key to locate.
195         * @param props The properties.
196         * @return The String after substitution.
197         */
198        public static String findAndSubst(final String key, final Properties props) {
199            final String value = props.getProperty(key);
200            if (value == null) {
201                return null;
202            }
203    
204            try {
205                return substVars(value, props);
206            } catch (final IllegalArgumentException e) {
207                LOGGER.error("Bad option value [{}].", value, e);
208                return value;
209            }
210        }
211    
212        /**
213         * Instantiate an object given a class name. Check that the
214         * <code>className</code> is a subclass of
215         * <code>superClass</code>. If that test fails or the object could
216         * not be instantiated, then <code>defaultValue</code> is returned.
217         *
218         * @param className    The fully qualified class name of the object to instantiate.
219         * @param superClass   The class to which the new object should belong.
220         * @param defaultValue The object to return in case of non-fulfillment
221         * @return The created object.
222         */
223        public static Object instantiateByClassName(final String className, final Class<?> superClass,
224                                             final Object defaultValue) {
225            if (className != null) {
226                try {
227                    final Class<?> classObj = Loader.loadClass(className);
228                    if (!superClass.isAssignableFrom(classObj)) {
229                        LOGGER.error("A \"{}\" object is not assignable to a \"{}\" variable.", className,
230                            superClass.getName());
231                        LOGGER.error("The class \"{}\" was loaded by [{}] whereas object of type [{}] was loaded by [{}].",
232                            superClass.getName(), superClass.getClassLoader(), classObj.getName());
233                        return defaultValue;
234                    }
235                    return classObj.newInstance();
236                } catch (final Exception e) {
237                    LOGGER.error("Could not instantiate class [{}].", className, e);
238                }
239            }
240            return defaultValue;
241        }
242    
243    
244        /**
245         * Perform variable substitution in string <code>val</code> from the
246         * values of keys found in the system propeties.
247         *
248         * <p>The variable substitution delimiters are <b>${</b> and <b>}</b>.</p>
249         *
250         * <p>For example, if the System properties contains "key=value", then
251         * the call</p>
252         * <pre>
253         * String s = OptionConverter.substituteVars("Value of key is ${key}.");
254         * </pre>
255         * <p>
256         * will set the variable <code>s</code> to "Value of key is value.".
257         * </p>
258         * <p>If no value could be found for the specified key, then the
259         * <code>props</code> parameter is searched, if the value could not
260         * be found there, then substitution defaults to the empty string.</p>
261         *
262         * <p>For example, if system properties contains no value for the key
263         * "inexistentKey", then the call
264         * </p>
265         * <pre>
266         * String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]");
267         * </pre>
268         * <p>
269         * will set <code>s</code> to "Value of inexistentKey is []"
270         * </p>
271         * <p>An {@link java.lang.IllegalArgumentException} is thrown if
272         * <code>val</code> contains a start delimeter "${" which is not
273         * balanced by a stop delimeter "}". </p>
274         *
275         * @param val The string on which variable substitution is performed.
276         * @param props The properties to use for substitution.
277         * @return The String after substitution.
278         * @throws IllegalArgumentException if <code>val</code> is malformed.
279         */
280        public static String substVars(final String val, final Properties props) throws
281            IllegalArgumentException {
282    
283            final StringBuilder sbuf = new StringBuilder();
284    
285            int i = 0;
286            int j;
287            int k;
288    
289            while (true) {
290                j = val.indexOf(DELIM_START, i);
291                if (j == -1) {
292                    // no more variables
293                    if (i == 0) { // this is a simple string
294                        return val;
295                    }
296                    // add the tail string which contails no variables and return the result.
297                    sbuf.append(val.substring(i, val.length()));
298                    return sbuf.toString();
299                }
300                sbuf.append(val.substring(i, j));
301                k = val.indexOf(DELIM_STOP, j);
302                if (k == -1) {
303                    throw new IllegalArgumentException('"' + val +
304                        "\" has no closing brace. Opening brace at position " + j
305                        + '.');
306                }
307                j += DELIM_START_LEN;
308                final String key = val.substring(j, k);
309                // first try in System properties
310                String replacement = PropertiesUtil.getProperties().getStringProperty(key, null);
311                // then try props parameter
312                if (replacement == null && props != null) {
313                    replacement = props.getProperty(key);
314                }
315    
316                if (replacement != null) {
317                    // Do variable substitution on the replacement string
318                    // such that we can solve "Hello ${x2}" as "Hello p1"
319                    // the where the properties are
320                    // x1=p1
321                    // x2=${x1}
322                    final String recursiveReplacement = substVars(replacement, props);
323                    sbuf.append(recursiveReplacement);
324                }
325                i = k + DELIM_STOP_LEN;
326            }
327        }
328    }