001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.shared.ldap.util;
021    
022    
023    
024    import java.io.File;
025    import java.io.InputStream;
026    import java.io.IOException;
027    import java.io.FileInputStream;
028    import java.util.Enumeration;
029    import java.util.Hashtable;
030    import java.util.Properties;
031    
032    import org.apache.directory.shared.ldap.NotImplementedException;
033    
034    
035    /**
036     * A utility class used for accessing, finding, merging and macro expanding
037     * properties, on disk, via URLS or as resources.
038     * 
039     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
040     * @version $Rev: 686082 $
041     */
042    public class PropertiesUtils
043    {
044        /** default properties file extension */
045        private static final String DOTPROPERTIES = ".properties";
046    
047    
048        // ------------------------------------------------------------------------
049        // Utilities for discovering Properties
050        // ------------------------------------------------------------------------
051    
052        /**
053         * Loads a properties object in a properties file if it exists relative to
054         * the filename ${user.home}. If the file ${user.home}/[filename] does not
055         * exist then one last attempt to find the file is made if filename does not
056         * have a .properties extension. If so and
057         * ${user.home}/[filename].properties exists then it is loaded.
058         * 
059         * @param filename
060         *            the properties file name with or without an extension
061         * @return the user properties object
062         */
063        public static Properties findUserProperties( String filename )
064        {
065            return findProperties( new File( System.getProperty( "user.home" ) ), filename );
066        }
067    
068    
069        /**
070         * Create a new properties object and load the properties file if it exists
071         * relative to [dir]/[filename] or [dir]/[filename].properties.
072         * 
073         * @param dir
074         *            the base directory
075         * @param filename
076         *            the full fine name or the base name w/o the extension
077         * @return the loaded properties object
078         */
079        public static Properties findProperties( File dir, String filename )
080        {
081            final File asis = new File( dir, filename );
082    
083            if ( asis.exists() )
084            {
085                return getProperties( asis );
086            }
087    
088            if ( filename.endsWith( DOTPROPERTIES ) )
089            {
090                String noExt = filename.substring( 0, filename.length() - 11 );
091                if ( new File( dir, noExt ).exists() )
092                {
093                    return getProperties( new File( dir, noExt ) );
094                }
095    
096                return new Properties();
097            }
098    
099            File withExt = new File( dir, filename + DOTPROPERTIES );
100            if ( withExt.exists() )
101            {
102                return getProperties( withExt );
103            }
104    
105            return new Properties();
106        }
107    
108    
109        /**
110         * Load a properties from a resource relative to a supplied class. First an
111         * attempt is made to locate a property file colocated with the class with
112         * the name [class].properties. If this cannot be found or errors result an
113         * empty Properties file is returned.
114         * 
115         * @param ref
116         *            a class to use for relative path references
117         * @return the static properties
118         */
119        public static Properties getStaticProperties( Class<?> ref )
120        {
121            final Properties properties = new Properties();
122            final String address = ref.toString().replace( '.', '/' );
123            final String path = address + ".properties";
124            InputStream input = ref.getResourceAsStream( path );
125    
126            if ( null != input )
127            {
128                try
129                {
130                    properties.load( input );
131                }
132                catch ( IOException e )
133                {
134                    return properties;
135                }
136            }
137    
138            return properties;
139        }
140    
141    
142        /**
143         * Load properties from a resource relative to a supplied class and path.
144         * 
145         * @param ref
146         *            a class to use for relative path references
147         * @param path
148         *            the relative path to the resoruce
149         * @return the static properties
150         */
151        public static Properties getStaticProperties( Class<?> ref, String path )
152        {
153            Properties properties = new Properties();
154            InputStream input = ref.getResourceAsStream( path );
155    
156            if ( input == null )
157            {
158                return properties;
159            }
160    
161            try
162            {
163                properties.load( input );
164            }
165            catch ( IOException e )
166            {
167                return properties;
168            }
169    
170            return properties;
171        }
172    
173    
174        /**
175         * Creates a properties object and loads the properties in the file
176         * otherwise and empty property object will be returned.
177         * 
178         * @param file
179         *            the properties file
180         * @return the properties object
181         */
182        public static Properties getProperties( File file )
183        {
184            Properties properties = new Properties();
185    
186            if ( null == file )
187            {
188                return properties;
189            }
190    
191            if ( file.exists() )
192            {
193                try
194                {
195                    final FileInputStream fis = new FileInputStream( file );
196                    try
197                    {
198                        properties.load( fis );
199                    }
200                    finally
201                    {
202                        fis.close();
203                    }
204                }
205                catch ( IOException e )
206                {
207                    return properties;
208                }
209            }
210    
211            return properties;
212        }
213    
214    
215        /**
216         * Loads a properties file as a CL resource if it exists and returns an
217         * empty Properties object otherwise.
218         * 
219         * @param classloader
220         *            the loader to use for the resources
221         * @param path
222         *            the path to the resource
223         * @return the loaded or new Properties
224         */
225        public static Properties getProperties( ClassLoader classloader, String path )
226        {
227            Properties properties = new Properties();
228            InputStream input = classloader.getResourceAsStream( path );
229    
230            if ( input != null )
231            {
232                try
233                {
234                    properties.load( input );
235                }
236                catch ( IOException e )
237                {
238                    return properties;
239                }
240            }
241    
242            return properties;
243        }
244    
245    
246        /**
247         * Loads a properties file as a class resource if it exists and returns an
248         * empty Properties object otherwise.
249         * 
250         * @param clazz
251         *            the class to use for resolving the resources
252         * @param path
253         *            the relative path to the resource
254         * @return the loaded or new Properties
255         */
256        public static Properties getProperties( Class<?> clazz, String path )
257        {
258            Properties properties = new Properties();
259            InputStream input = clazz.getResourceAsStream( path );
260    
261            if ( input != null )
262            {
263                try
264                {
265                    properties.load( input );
266                }
267                catch ( IOException e )
268                {
269                    return properties;
270                }
271            }
272    
273            return properties;
274        }
275    
276    
277        // ------------------------------------------------------------------------
278        // Utilities for operating on or setting Properties values
279        // ------------------------------------------------------------------------
280    
281        /**
282         * Expands out a set of property key macros in the following format
283         * ${foo.bar} where foo.bar is a property key, by dereferencing the value of
284         * the key using the original source Properties and other optional
285         * Properties. If the original expanded Properties contain the value for the
286         * macro key, foo.bar, then dereferencing stops by using the value in the
287         * expanded Properties: the other optional Properties are NOT used at all.
288         * If the original expanded Properties do NOT contain the value for the
289         * macro key, then the optional Properties are used in order. The first of
290         * the optionals to contain the value for the macro key (foo.bar) shorts the
291         * search. Hence the first optional Properties in the array to contain a
292         * value for the macro key (foo.bar) is used to set the expanded value. If a
293         * macro cannot be expanded because it's key was not defined within the
294         * expanded Properties or one of the optional Properties then it is left as
295         * is.
296         * 
297         * @param expanded
298         *            the Properties to perform the macro expansion upon
299         * @param optionals
300         *            null or an optional set of Properties to use for dereferencing
301         *            macro keys (foo.bar)
302         */
303        public static void macroExpand( Properties expanded, Properties[] optionals )
304        {
305            // Handle null optionals
306            if ( null == optionals )
307            {
308                optionals = new Properties[0];
309            }
310    
311            Enumeration<?> list = expanded.propertyNames();
312            
313            while ( list.hasMoreElements() )
314            {
315                String key = ( String ) list.nextElement();
316                String macro = expanded.getProperty( key );
317    
318                int n = macro.indexOf( "${" );
319                if ( n < 0 )
320                {
321                    continue;
322                }
323    
324                int m = macro.indexOf( "}", n + 2 );
325                if ( m < 0 )
326                {
327                    continue;
328                }
329    
330                final String symbol = macro.substring( n + 2, m );
331    
332                if ( expanded.containsKey( symbol ) )
333                {
334                    final String value = expanded.getProperty( symbol );
335                    final String head = macro.substring( 0, n );
336                    final String tail = macro.substring( m + 1 );
337                    final String resolved = head + value + tail;
338                    expanded.put( key, resolved );
339                    continue;
340                }
341    
342                /*
343                 * Check if the macro key exists within the array of optional
344                 * Properties. Set expanded value to first Properties with the key
345                 * and break out of the loop.
346                 */
347                for ( int ii = 0; ii < optionals.length; ii++ )
348                {
349                    if ( optionals[ii].containsKey( symbol ) )
350                    {
351                        final String value = optionals[ii].getProperty( symbol );
352                        final String head = macro.substring( 0, n );
353                        final String tail = macro.substring( m + 1 );
354                        final String resolved = head + value + tail;
355                        expanded.put( key, resolved );
356                        break;
357                    }
358                }
359            }
360        }
361    
362    
363        /**
364         * Discovers a value within a set of Properties either halting on the first
365         * time the property is discovered or continuing on to take the last value
366         * found for the property key.
367         * 
368         * @param key
369         *            a property key
370         * @param sources
371         *            a set of source Properties
372         * @param haltOnDiscovery
373         *            true if we stop on finding a value, false otherwise
374         * @return the value found or null
375         */
376        public static String discover( String key, Properties[] sources, boolean haltOnDiscovery )
377        {
378            String retval = null;
379    
380            for ( int ii = 0; ii < sources.length; ii++ )
381            {
382                if ( sources[ii].containsKey( key ) )
383                {
384                    retval = sources[ii].getProperty( key );
385    
386                    if ( haltOnDiscovery )
387                    {
388                        break;
389                    }
390                }
391            }
392    
393            return retval;
394        }
395    
396    
397        /**
398         * Merges a set of properties from source Properties into a target
399         * properties instance containing keys. This method does not allow null
400         * overrides.
401         * 
402         * @param keys
403         *            the keys to discover values for
404         * @param sources
405         *            the sources to search
406         * @param haltOnDiscovery
407         *            true to halt on first find or false to continue to last find
408         */
409        public static void discover( Properties keys, Properties[] sources, boolean haltOnDiscovery )
410        {
411            if ( null == sources || null == keys )
412            {
413                return;
414            }
415    
416            /*
417             * H A N D L E S I N G L E V A L U E D K E Y S
418             */
419            for ( Object key:keys.keySet() )
420            {
421                String value = discover( (String)key, sources, haltOnDiscovery );
422    
423                if ( value != null )
424                {
425                    keys.setProperty( (String)key, value );
426                }
427            }
428        }
429    
430    
431        // ------------------------------------------------------------------------
432        // Various Property Accessors
433        // ------------------------------------------------------------------------
434    
435        /**
436         * Gets a String property as a boolean returning a defualt if the key is not
437         * present. In any case, true, on, 1 and yes strings return true and
438         * everything else returns
439         * 
440         * @param props
441         *            the properties to get the value from
442         * @param key
443         *            the property key
444         * @param defaultValue
445         *            the default value to return if key is not present
446         * @return true defaultValue if property does not exist, else return true if
447         *         the String value is one of 'true', 'on', '1', 'yes', otherwise
448         *         false is returned
449         */
450        public static boolean get( Properties props, String key, boolean defaultValue )
451        {
452            if ( props == null || !props.containsKey( key ) || props.getProperty( key ) == null )
453            {
454                return defaultValue;
455            }
456    
457            String val = props.getProperty( key ).trim().toLowerCase();
458            return val.equals( "true" ) || val.equals( "on" ) || val.equals( "1" ) || val.equals( "yes" );
459        }
460    
461    
462        /**
463         * Gets a property or entry value from a hashtable and tries to transform
464         * whatever the value may be to an primitive integer.
465         * 
466         * @param ht
467         *            the hashtable to access for the value
468         * @param key
469         *            the key to use when accessing the ht
470         * @param defval
471         *            the default value to use if the key is not contained in ht or
472         *            if the value cannot be represented as a primitive integer.
473         * @return the primitive integer representation of a hashtable value
474         */
475        public static int get( Hashtable<String, Object> ht, Object key, int defval )
476        {
477            if ( ht == null || !ht.containsKey( key ) || ht.get( key ) == null )
478            {
479                return defval;
480            }
481    
482            Object obj = ht.get( key );
483    
484            if ( obj instanceof Byte )
485            {
486                return ( ( Byte ) obj ).intValue();
487            }
488            if ( obj instanceof Short )
489            {
490                return ( ( Short ) obj ).intValue();
491            }
492            if ( obj instanceof Integer )
493            {
494                return ( ( Integer ) obj ).intValue();
495            }
496            if ( obj instanceof Long )
497            {
498                return ( ( Long ) obj ).intValue();
499            }
500            if ( obj instanceof String )
501            {
502                try
503                {
504                    return Integer.parseInt( ( String ) obj );
505                }
506                catch ( NumberFormatException ne )
507                {
508                    ne.printStackTrace();
509                    return defval;
510                }
511            }
512    
513            return defval;
514        }
515    
516    
517        public static long get( Properties props, String key, long defaultValue )
518        {
519            if ( props == null || !props.containsKey( key ) || props.getProperty( key ) == null )
520            {
521                return defaultValue;
522            }
523    
524            throw new NotImplementedException();
525        }
526    
527    
528        public static byte get( Properties props, String key, byte defaultValue )
529        {
530            if ( props == null || !props.containsKey( key ) || props.getProperty( key ) == null )
531            {
532                return defaultValue;
533            }
534    
535            throw new NotImplementedException();
536        }
537    
538    
539        public static char get( Properties props, String key, char defaultValue )
540        {
541            if ( props == null || !props.containsKey( key ) || props.getProperty( key ) == null )
542            {
543                return defaultValue;
544            }
545    
546            throw new NotImplementedException();
547        }
548    }