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