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.helpers;
018
019import java.util.Locale;
020import java.util.Properties;
021
022import org.apache.logging.log4j.Logger;
023import org.apache.logging.log4j.status.StatusLogger;
024import org.apache.logging.log4j.util.PropertiesUtil;
025
026/**
027 * A convenience class to convert property values to specific types.
028 */
029public 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 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 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("[" + s + "] is not in proper int form.");
140                e.printStackTrace();
141            }
142        }
143        return defaultValue;
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 str = value.trim().toUpperCase(Locale.ENGLISH);
158        long multiplier = 1;
159        int index;
160
161        if ((index = str.indexOf("KB")) != -1) {
162            multiplier = ONE_K;
163            str = str.substring(0, index);
164        } else if ((index = str.indexOf("MB")) != -1) {
165            multiplier = ONE_K * ONE_K;
166            str = str.substring(0, index);
167        } else if ((index = str.indexOf("GB")) != -1) {
168            multiplier = ONE_K * ONE_K * ONE_K;
169            str = str.substring(0, index);
170        }
171        if (str != null) {
172            try {
173                return Long.parseLong(str) * multiplier;
174            } catch (final NumberFormatException e) {
175                LOGGER.error("[" + str + "] 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 delimiters 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 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     * 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     *
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}