001package org.apache.commons.jcs3.utils.config;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.Properties;
023
024import org.apache.commons.jcs3.log.Log;
025import org.apache.commons.jcs3.log.LogManager;
026
027/**
028 * This class is based on the log4j class org.apache.log4j.helpers.OptionConverter that was made by
029 * Ceki Gülcü Simon Kitching; Avy Sharell (sharell@online.fr) Anders Kristensen Matthieu
030 * Verbert (mve@zurich.ibm.com) A convenience class to convert property values to specific types.
031 */
032public class OptionConverter
033{
034    /** The logger */
035    private static final Log log = LogManager.getLog( OptionConverter.class );
036
037    /** System property delimter */
038    private static final String DELIM_START = "${";
039
040    /** System property delimter */
041    private static final char DELIM_STOP = '}';
042
043    /** System property delimter start length */
044    private static final int DELIM_START_LEN = 2;
045
046    /** System property delimter end length */
047    private static final int DELIM_STOP_LEN = 1;
048
049    /** No instances please. */
050    private OptionConverter()
051    {
052    }
053
054    /**
055     * Combines two arrays.
056     * @param l
057     * @param r
058     * @return String[]
059     */
060    public static String[] concatanateArrays( final String[] l, final String[] r )
061    {
062        final int len = l.length + r.length;
063        final String[] a = new String[len];
064
065        System.arraycopy( l, 0, a, 0, l.length );
066        System.arraycopy( r, 0, a, l.length, r.length );
067
068        return a;
069    }
070
071    /**
072     * Escapes special characters.
073     *
074     * @param s
075     * @return String
076     */
077    public static String convertSpecialChars( final String s )
078    {
079        char c;
080        final int len = s.length();
081        final StringBuilder sb = new StringBuilder( len );
082
083        int i = 0;
084        while ( i < len )
085        {
086            c = s.charAt( i++ );
087            if ( c == '\\' )
088            {
089                c = s.charAt( i++ );
090                if ( c == 'n' )
091                {
092                    c = '\n';
093                }
094                else if ( c == 'r' )
095                {
096                    c = '\r';
097                }
098                else if ( c == 't' )
099                {
100                    c = '\t';
101                }
102                else if ( c == 'f' )
103                {
104                    c = '\f';
105                }
106                else if ( c == '\b' )
107                {
108                    c = '\b';
109                }
110                else if ( c == '\"' )
111                {
112                    c = '\"';
113                }
114                else if ( c == '\'' )
115                {
116                    c = '\'';
117                }
118                else if ( c == '\\' )
119                {
120                    c = '\\';
121                }
122            }
123            sb.append( c );
124        }
125        return sb.toString();
126    }
127
128    /**
129     * Very similar to <code>System.getProperty</code> except that the {@link SecurityException} is
130     * hidden.
131     * @param key The key to search for.
132     * @param def The default value to return.
133     * @return the string value of the system property, or the default value if there is no property
134     *         with that key.
135     * @since 1.1
136     */
137
138    public static String getSystemProperty( final String key, final String def )
139    {
140        try
141        {
142            return System.getProperty( key, def );
143        }
144        catch ( final Throwable e )
145        {
146            // MS-Java throws com.ms.security.SecurityExceptionEx
147            log.debug( "Was not allowed to read system property \"{0}\".", key );
148            return def;
149        }
150    }
151
152    /**
153     * Creates an object for the className value of the key.
154     *
155     * @param props
156     * @param key
157     * @param defaultValue
158     * @return Object that was created
159     */
160    public static <T> T instantiateByKey( final Properties props, final String key, final T defaultValue )
161    {
162
163        // Get the value of the property in string form
164        final String className = findAndSubst( key, props );
165        if ( className == null )
166        {
167            log.trace( "Could not find value for key {0}", key );
168            return defaultValue;
169        }
170        // Trim className to avoid trailing spaces that cause problems.
171        return OptionConverter.instantiateByClassName( className.trim(), defaultValue );
172    }
173
174    /**
175     * If <code>value</code> is "true", then {@code true} is returned. If <code>value</code> is
176     * "false", then {@code true} is returned. Otherwise, <code>default</code> is returned.
177     *
178     * Case of value is unimportant.
179     * @param value
180     * @param defaultValue
181     * @return Object
182     */
183    public static boolean toBoolean( final String value, final boolean defaultValue )
184    {
185        if ( value == null )
186        {
187            return defaultValue;
188        }
189        final String trimmedVal = value.trim();
190        if ( "true".equalsIgnoreCase( trimmedVal ) )
191        {
192            return true;
193        }
194        if ( "false".equalsIgnoreCase( trimmedVal ) )
195        {
196            return false;
197        }
198        return defaultValue;
199    }
200
201    /**
202     * Converts to int.
203     *
204     * @param value
205     * @param defaultValue
206     * @return int
207     */
208    public static int toInt( final String value, final int defaultValue )
209    {
210        if ( value != null )
211        {
212            final String s = value.trim();
213            try
214            {
215                return Integer.parseInt(s);
216            }
217            catch ( final NumberFormatException e )
218            {
219                log.error( "[{0}] is not in proper int form.", s, e );
220            }
221        }
222        return defaultValue;
223    }
224
225    /**
226     * @param value
227     * @param defaultValue
228     * @return long
229     */
230    public static long toFileSize( final String value, final long defaultValue )
231    {
232        if ( value == null )
233        {
234            return defaultValue;
235        }
236
237        String s = value.trim().toUpperCase();
238        long multiplier = 1;
239        int index;
240
241        if ( ( index = s.indexOf( "KB" ) ) != -1 )
242        {
243            multiplier = 1024;
244            s = s.substring( 0, index );
245        }
246        else if ( ( index = s.indexOf( "MB" ) ) != -1 )
247        {
248            multiplier = 1024 * 1024;
249            s = s.substring( 0, index );
250        }
251        else if ( ( index = s.indexOf( "GB" ) ) != -1 )
252        {
253            multiplier = 1024 * 1024 * 1024;
254            s = s.substring( 0, index );
255        }
256        try
257        {
258            return Long.parseLong(s) * multiplier;
259        }
260        catch ( final NumberFormatException e )
261        {
262            log.error( "[{0}] is not in proper int form.", s);
263            log.error( "[{0}] not in expected format", value, e );
264        }
265        return defaultValue;
266    }
267
268    /**
269     * Find the value corresponding to <code>key</code> in <code>props</code>. Then perform variable
270     * substitution on the found value.
271     *
272     * @param key
273     * @param props
274     * @return substituted string
275     */
276
277    public static String findAndSubst( final String key, final Properties props )
278    {
279        final String value = props.getProperty( key );
280        if ( value == null )
281        {
282            return null;
283        }
284
285        try
286        {
287            return substVars( value, props );
288        }
289        catch ( final IllegalArgumentException e )
290        {
291            log.error( "Bad option value [{0}]", value, e );
292            return value;
293        }
294    }
295
296    /**
297     * Instantiate an object given a class name. Check that the <code>className</code> is a subclass
298     * of <code>superClass</code>. If that test fails or the object could not be instantiated, then
299     * <code>defaultValue</code> is returned.
300     *
301     * @param className The fully qualified class name of the object to instantiate.
302     * @param defaultValue The object to return in case of non-fulfillment
303     * @return instantiated object
304     */
305
306    public static <T> T instantiateByClassName( final String className, final T defaultValue )
307    {
308        if ( className != null )
309        {
310            try
311            {
312                final Class<?> classObj = Class.forName( className );
313                final Object o = classObj.getDeclaredConstructor().newInstance();
314
315                try
316                {
317                    @SuppressWarnings("unchecked") // CCE catched
318                    final
319                    T t = (T) o;
320                    return t;
321                }
322                catch (final ClassCastException e)
323                {
324                    log.error( "A \"{0}\" object is not assignable to the "
325                            + "generic variable.", className );
326                    return defaultValue;
327                }
328            }
329            catch (final Exception e )
330            {
331                log.error( "Could not instantiate class [{0}]", className, e );
332            }
333        }
334        return defaultValue;
335    }
336
337    /**
338     * Perform variable substitution in string <code>val</code> from the values of keys found in the
339     * system properties.
340     *
341     * The variable substitution delimiters are <b>${ </b> and <b>} </b>.
342     *
343     * For example, if the System properties contains "key=value", then the call
344     *
345     * <pre>
346     * String s = OptionConverter.substituteVars( &quot;Value of key is ${key}.&quot; );
347     * </pre>
348     *
349     * will set the variable <code>s</code> to "Value of key is value.".
350     *
351     * If no value could be found for the specified key, then the <code>props</code> parameter is
352     * searched, if the value could not be found there, then substitution defaults to the empty
353     * string.
354     *
355     * For example, if system properties contains no value for the key "inexistentKey", then the call
356     *
357     * <pre>
358     * String s = OptionConverter.subsVars( &quot;Value of inexistentKey is [${inexistentKey}]&quot; );
359     * </pre>
360     *
361     * will set <code>s</code> to "Value of inexistentKey is []"
362     * <p>
363     * An {@link java.lang.IllegalArgumentException}is thrown if <code>val</code> contains a start
364     * delimiter "${" which is not balanced by a stop delimiter "}".
365     * </p>
366     * <p>
367     * <b>Author </b> Avy Sharell
368     * </p>
369     * @param val The string on which variable substitution is performed.
370     * @param props
371     * @return String
372     * @throws IllegalArgumentException if <code>val</code> is malformed.
373     */
374
375    public static String substVars( final String val, final Properties props )
376        throws IllegalArgumentException
377    {
378        final StringBuilder sbuf = new StringBuilder();
379
380        int i = 0;
381        int j;
382        int k;
383
384        while ( true )
385        {
386            j = val.indexOf( DELIM_START, i );
387            if ( j == -1 )
388            {
389                if ( i == 0 )
390                {
391                    return val;
392                }
393                sbuf.append( val.substring( i ) );
394                return sbuf.toString();
395            }
396            sbuf.append(val, i, j);
397            k = val.indexOf( DELIM_STOP, j );
398            if ( k == -1 )
399            {
400                throw new IllegalArgumentException( '"' + val + "\" has no closing brace. Opening brace at position "
401                    + j + '.' );
402            }
403            j += DELIM_START_LEN;
404            final String key = val.substring( j, k );
405            // first try in System properties
406            String replacement = getSystemProperty( key, null );
407            // then try props parameter
408            if ( replacement == null && props != null )
409            {
410                replacement = props.getProperty( key );
411            }
412
413            if ( replacement != null )
414            {
415                sbuf.append( replacement );
416            }
417            i = k + DELIM_STOP_LEN;
418        }
419    }
420}