    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    }