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