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         * <p/>
119         * <p>Case of value is unimportant.
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            if (str != null) {
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            }
189            return defaultValue;
190        }
191    
192        /**
193         * Find the value corresponding to <code>key</code> in
194         * <code>props</code>. Then perform variable substitution on the
195         * found value.
196         * @param key The key to locate.
197         * @param props The properties.
198         * @return The String after substitution.
199         */
200        public static String findAndSubst(final String key, final Properties props) {
201            final String value = props.getProperty(key);
202            if (value == null) {
203                return null;
204            }
205    
206            try {
207                return substVars(value, props);
208            } catch (final IllegalArgumentException e) {
209                LOGGER.error("Bad option value [{}].", value, e);
210                return value;
211            }
212        }
213    
214        /**
215         * Instantiate an object given a class name. Check that the
216         * <code>className</code> is a subclass of
217         * <code>superClass</code>. If that test fails or the object could
218         * not be instantiated, then <code>defaultValue</code> is returned.
219         *
220         * @param className    The fully qualified class name of the object to instantiate.
221         * @param superClass   The class to which the new object should belong.
222         * @param defaultValue The object to return in case of non-fulfillment
223         * @return The created object.
224         */
225        public static Object instantiateByClassName(final String className, final Class<?> superClass,
226                                             final Object defaultValue) {
227            if (className != null) {
228                try {
229                    final Class<?> classObj = Loader.loadClass(className);
230                    if (!superClass.isAssignableFrom(classObj)) {
231                        LOGGER.error("A \"{}\" object is not assignable to a \"{}\" variable.", className,
232                            superClass.getName());
233                        LOGGER.error("The class \"{}\" was loaded by [{}] whereas object of type [{}] was loaded by [{}].",
234                            superClass.getName(), superClass.getClassLoader(), classObj.getName());
235                        return defaultValue;
236                    }
237                    return classObj.newInstance();
238                } catch (final Exception e) {
239                    LOGGER.error("Could not instantiate class [{}].", className, e);
240                }
241            }
242            return defaultValue;
243        }
244    
245    
246        /**
247         * Perform variable substitution in string <code>val</code> from the
248         * values of keys found in the system propeties.
249         * <p/>
250         * <p>The variable substitution delimiters are <b>${</b> and <b>}</b>.
251         * <p/>
252         * <p>For example, if the System properties contains "key=value", then
253         * the call
254         * <pre>
255         * String s = OptionConverter.substituteVars("Value of key is ${key}.");
256         * </pre>
257         * <p/>
258         * will set the variable <code>s</code> to "Value of key is value.".
259         * <p/>
260         * <p>If no value could be found for the specified key, then the
261         * <code>props</code> parameter is searched, if the value could not
262         * be found there, then substitution defaults to the empty string.
263         * <p/>
264         * <p>For example, if system properties contains no value for the key
265         * "inexistentKey", then the call
266         * <p/>
267         * <pre>
268         * String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]");
269         * </pre>
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         * <p/>
276         *
277         * @param val The string on which variable substitution is performed.
278         * @param props The properties to use for substitution.
279         * @return The String after substitution.
280         * @throws IllegalArgumentException if <code>val</code> is malformed.
281         */
282        public static String substVars(final String val, final Properties props) throws
283            IllegalArgumentException {
284    
285            final StringBuilder sbuf = new StringBuilder();
286    
287            int i = 0;
288            int j;
289            int k;
290    
291            while (true) {
292                j = val.indexOf(DELIM_START, i);
293                if (j == -1) {
294                    // no more variables
295                    if (i == 0) { // this is a simple string
296                        return val;
297                    }
298                    // add the tail string which contails no variables and return the result.
299                    sbuf.append(val.substring(i, val.length()));
300                    return sbuf.toString();
301                }
302                sbuf.append(val.substring(i, j));
303                k = val.indexOf(DELIM_STOP, j);
304                if (k == -1) {
305                    throw new IllegalArgumentException('"' + val +
306                        "\" has no closing brace. Opening brace at position " + j
307                        + '.');
308                }
309                j += DELIM_START_LEN;
310                final String key = val.substring(j, k);
311                // first try in System properties
312                String replacement = PropertiesUtil.getProperties().getStringProperty(key, null);
313                // then try props parameter
314                if (replacement == null && props != null) {
315                    replacement = props.getProperty(key);
316                }
317    
318                if (replacement != null) {
319                    // Do variable substitution on the replacement string
320                    // such that we can solve "Hello ${x2}" as "Hello p1"
321                    // the where the properties are
322                    // x1=p1
323                    // x2=${x1}
324                    final String recursiveReplacement = substVars(replacement, props);
325                    sbuf.append(recursiveReplacement);
326                }
327                i = k + DELIM_STOP_LEN;
328            }
329        }
330    }