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.helpers;
018    
019    import org.apache.logging.log4j.Logger;
020    import org.apache.logging.log4j.status.StatusLogger;
021    import org.apache.logging.log4j.util.PropertiesUtil;
022    
023    import java.util.Locale;
024    import java.util.Properties;
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[] concatanateArrays(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 StringBuffer sbuf = new StringBuffer(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 true} 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 dEfault 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 dEfault) {
114            if (value == null) {
115                return dEfault;
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 dEfault;
125        }
126    
127        /**
128         * Convert the String value to an int.
129         * @param value The value as a String.
130         * @param dEfault The default value.
131         * @return The value as an int.
132         */
133        public static int toInt(final String value, final int dEfault) {
134            if (value != null) {
135                final String s = value.trim();
136                try {
137                    return Integer.valueOf(s);
138                } catch (final NumberFormatException e) {
139                    LOGGER.error("[" + s + "] is not in proper int form.");
140                    e.printStackTrace();
141                }
142            }
143            return dEfault;
144        }
145    
146        /**
147         *
148         * @param value The size of the file as a String.
149         * @param defaultValue The default value.
150         * @return The size of the file as a long.
151         */
152        public static long toFileSize(final String value, final long defaultValue) {
153            if (value == null) {
154                return defaultValue;
155            }
156    
157            String s = value.trim().toUpperCase(Locale.ENGLISH);
158            long multiplier = 1;
159            int index;
160    
161            if ((index = s.indexOf("KB")) != -1) {
162                multiplier = ONE_K;
163                s = s.substring(0, index);
164            } else if ((index = s.indexOf("MB")) != -1) {
165                multiplier = ONE_K * ONE_K;
166                s = s.substring(0, index);
167            } else if ((index = s.indexOf("GB")) != -1) {
168                multiplier = ONE_K * ONE_K * ONE_K;
169                s = s.substring(0, index);
170            }
171            if (s != null) {
172                try {
173                    return Long.valueOf(s) * multiplier;
174                } catch (final NumberFormatException e) {
175                    LOGGER.error("[" + s + "] is not in proper int form.");
176                    LOGGER.error("[" + value + "] not in expected format.", e);
177                }
178            }
179            return defaultValue;
180        }
181    
182        /**
183         * Find the value corresponding to <code>key</code> in
184         * <code>props</code>. Then perform variable substitution on the
185         * found value.
186         * @param key The key to locate.
187         * @param props The properties.
188         * @return The String after substitution.
189         */
190        public static String findAndSubst(final String key, final Properties props) {
191            final String value = props.getProperty(key);
192            if (value == null) {
193                return null;
194            }
195    
196            try {
197                return substVars(value, props);
198            } catch (final IllegalArgumentException e) {
199                LOGGER.error("Bad option value [" + value + "].", e);
200                return value;
201            }
202        }
203    
204        /**
205         * Instantiate an object given a class name. Check that the
206         * <code>className</code> is a subclass of
207         * <code>superClass</code>. If that test fails or the object could
208         * not be instantiated, then <code>defaultValue</code> is returned.
209         *
210         * @param className    The fully qualified class name of the object to instantiate.
211         * @param superClass   The class to which the new object should belong.
212         * @param defaultValue The object to return in case of non-fulfillment
213         * @return The created object.
214         */
215        public static Object instantiateByClassName(final String className, final Class<?> superClass,
216                                             final Object defaultValue) {
217            if (className != null) {
218                try {
219                    final Class<?> classObj = Loader.loadClass(className);
220                    if (!superClass.isAssignableFrom(classObj)) {
221                        LOGGER.error("A \"" + className + "\" object is not assignable to a \"" +
222                            superClass.getName() + "\" variable.");
223                        LOGGER.error("The class \"" + superClass.getName() + "\" was loaded by ");
224                        LOGGER.error("[" + superClass.getClassLoader() + "] whereas object of type ");
225                        LOGGER.error("\"" + classObj.getName() + "\" was loaded by ["
226                            + classObj.getClassLoader() + "].");
227                        return defaultValue;
228                    }
229                    return classObj.newInstance();
230                } catch (final ClassNotFoundException e) {
231                    LOGGER.error("Could not instantiate class [" + className + "].", e);
232                } catch (final IllegalAccessException e) {
233                    LOGGER.error("Could not instantiate class [" + className + "].", e);
234                } catch (final InstantiationException e) {
235                    LOGGER.error("Could not instantiate class [" + className + "].", e);
236                } catch (final RuntimeException 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         * <p/>
248         * <p>The variable substitution delimeters are <b>${</b> and <b>}</b>.
249         * <p/>
250         * <p>For example, if the System properties contains "key=value", then
251         * the call
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.
261         * <p/>
262         * <p>For example, if system propeties 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         * will set <code>s</code> to "Value of inexistentKey is []"
269         * <p/>
270         * <p>An {@link java.lang.IllegalArgumentException} is thrown if
271         * <code>val</code> contains a start delimeter "${" which is not
272         * balanced by a stop delimeter "}". </p>
273         * <p/>
274         * <p><b>Author</b> Avy Sharell</a></p>
275         *
276         * @param val The string on which variable substitution is performed.
277         * @param props The properties to use for substitution.
278         * @return The String after substitution.
279         * @throws IllegalArgumentException if <code>val</code> is malformed.
280         */
281        public static String substVars(final String val, final Properties props) throws
282            IllegalArgumentException {
283    
284            final StringBuilder sbuf = new StringBuilder();
285    
286            int i = 0;
287            int j;
288            int k;
289    
290            while (true) {
291                j = val.indexOf(DELIM_START, i);
292                if (j == -1) {
293                    // no more variables
294                    if (i == 0) { // this is a simple string
295                        return val;
296                    } else { // 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                } else {
301                    sbuf.append(val.substring(i, j));
302                    k = val.indexOf(DELIM_STOP, j);
303                    if (k == -1) {
304                        throw new IllegalArgumentException('"' + val +
305                            "\" has no closing brace. Opening brace at position " + j
306                            + '.');
307                    } else {
308                        j += DELIM_START_LEN;
309                        final String key = val.substring(j, k);
310                        // first try in System properties
311                        String replacement = PropertiesUtil.getProperties().getStringProperty(key, null);
312                        // then try props parameter
313                        if (replacement == null && props != null) {
314                            replacement = props.getProperty(key);
315                        }
316    
317                        if (replacement != null) {
318                            // Do variable substitution on the replacement string
319                            // such that we can solve "Hello ${x2}" as "Hello p1"
320                            // the where the properties are
321                            // x1=p1
322                            // x2=${x1}
323                            final String recursiveReplacement = substVars(replacement, props);
324                            sbuf.append(recursiveReplacement);
325                        }
326                        i = k + DELIM_STOP_LEN;
327                    }
328                }
329            }
330        }
331    }