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    import org.apache.logging.log4j.util.Strings;
026    
027    /**
028     * A convenience class to convert property values to specific types.
029     */
030    public 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    }