Coverage details for org.chwf.config.ConfigFactory

LineHitsSource
1 /*
2 Chrysalis Web Framework [http://chrysalis.sourceforge.net]
3 Copyright (c) 2002, 2003, 2004, Paul Strack
4  
5 All rights reserved.
6  
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9  
10 1. Redistributions of source code must retain the above copyright notice, this
11 list of conditions and the following disclaimer.
12  
13 2. Redistributions in binary form must reproduce the above copyright notice,
14 this list of conditions and the following disclaimer in the documentation
15 and/or other materials provided with the distribution.
16  
17 3. Neither the name of the copyright holder nor the names of its contributors
18 may be used to endorse or promote products derived from this software without
19 specific prior written permission.
20  
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
25 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32  
33 package org.chwf.config;
34  
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.util.Collections;
38 import java.util.Locale;
39 import java.util.Map;
40 import java.util.Properties;
41 import java.util.SortedMap;
42 import java.util.TreeMap;
43 import java.util.WeakHashMap;
44  
45 import javax.xml.parsers.SAXParser;
46 import javax.xml.parsers.SAXParserFactory;
47  
48 import org.chwf.util.SequencedHashMap;
49  
50 /**
51  * @author Paul Strack
52  */
530public abstract class ConfigFactory {
54  
55   /** Property for location of extra configuration: "extra.xml.config". */
56   static final String EXTRA_XML_CONFIG = "extra.xml.config";
57  
58   /**
59    * A map for storing Config objects. This map holds its
60    * contents via weak references so that references can be
61    * deleted if application memory becomes an issue. The
62    * deleted Config object will be reloaded when requested.
63    */
642  static final Map CONFIG_MAP = new WeakHashMap();
65  
66   /** Constant for empty string. */
67   static final String EMPTY_STRING = "";
68  
69   /** Constant for underscore. */
70   static final char UNDERSCORE = '_';
71  
72   /** Character delimiting sub-names in configuration keys. */
73   static final char KEY_DELIMITER = '.';
74  
75   /** File extension for properties. */
76   static final String PROPERTY_FILE_EXTENSION = ".properties";
77  
78   /** File extension for xml. */
79   static final String XML_FILE_EXTENSION = ".xml";
80  
81   /** Base file name for package-level resources. */
82   static final String PACKAGE_RESOURCE = "chrysalis";
83  
84   /** Length of package resource name. */
851  static final int PACKAGE_RESOURCE_LENGTH = PACKAGE_RESOURCE.length();
86  
87   /** Limit to number of rounds of property expansion. */
88   static final int EXPANSION_LIMIT = 100;
89  
90   /** String used to mark start of property expansion point. */
91   static final String EXPANSION_START = "${";
92  
93   /** String used to mark end of property expansion point. */
94   static final String EXPANSION_END = "}";
95  
96   /** File separator abbreviation. */
97   static final String FILE_SEPARATOR_ABBREVIATION = "/";
98  
99   /** Path separator abbrevation. */
100   static final String PATH_SEPARATOR_ABBREVIATION = ":";
101  
102   /** File separator system property key. */
103   static final String FILE_SEPARATOR = "file.separator";
104  
105   /** Path separator system property key. */
106   static final String PATH_SEPARATOR = "path.separator";
107  
108   /** Empty sorted map singleton. */
1091  static final SortedMap EMPTY_SORTED_MAP =
110     Collections.unmodifiableSortedMap(new TreeMap());
111  
112   /**
113    * <p>Factory method for Config class.</p>
114    *
115    * @param contextClass The context class for the configuration.
116    * @return The Config object for the given class.
117    * @throws ConfigurationException For errors in loading configuration data.
118    */
119   public static Config getConfig(Class contextClass)
120     throws ConfigurationException {
121367    return getConfig(contextClass.getName(), null);
122   }
123  
124   /**
125    * <p>Factory method for Config class.</p>
126    *
127    * @param contextClass The context class for the configuration.
128    * @param locale The locale.
129    * @return The Config object for the given class.
130    * @throws ConfigurationException For errors in loading configuration data.
131    */
132   public static Config getConfig(Class contextClass, Locale locale)
133     throws ConfigurationException {
134420    return getConfig(contextClass.getName(), locale);
135   }
136  
137   /**
138    * Factory method for Config class. The resource name should be the location
139    * of the file in the classpath, with "." separating directories rather than
140    * "/". If you use "/" or your operating systems path separator, they will be
141    * replaced by ".". The resource name should omit the file extension
142    * (".properties" or ".xml"). This factory method can be used for resources
143    * that are not associated with any particular class.<p>
144    *
145    * @param resource The name of the resource.
146    * @return The Config object for the given resource.
147    * @throws ConfigurationException For errors in loading configuration data.
148    */
149   public static Config getConfig(String resource)
150     throws ConfigurationException {
1515    return getConfig(resource, null);
152   }
153  
154   /**
155    * Factory method for Config class. As {@link #getConfig(java.lang.String)},
156    * but include a locale for internationalization.<p>
157    *
158    * @param resource The name of the resource.
159    * @param locale The locale.
160    * @return The Config object for the given resource.
161    * @throws ConfigurationException For errors in loading configuration data.
162    */
163   public static Config getConfig(String resource, Locale locale)
164     throws ConfigurationException {
165  
1664870    resource = resource.replace('/', '.');
1674870    Config config = null;
1684870    String localizedResource = resource + deriveLocaleName(locale);
169  
170     // Synchronize on the config map for thread safety
1714870    synchronized (CONFIG_MAP) {
1724870      config = (Config) CONFIG_MAP.get(localizedResource);
1734870      if (config == null) {
1743544        config = new ConfigImpl(resource, locale);
1753542        CONFIG_MAP.put(localizedResource, config);
176       }
1774868    }
1784868    return config;
179   }
180  
181   /**
182    * Derive name for Locale object.
183    *
184    * @param locale The locale
185    * @return The name
186    */
187   static String deriveLocaleName(Locale locale) {
1889331    if (locale == null) {
1896802      return EMPTY_STRING;
190     } else {
1912529      if ((locale.getCountry() == null)
192         || locale.getCountry().equals(EMPTY_STRING)) {
1931451        return UNDERSCORE + locale.getLanguage();
194       } else {
1951078        return UNDERSCORE
196           + locale.getLanguage()
197           + UNDERSCORE
198           + locale.getCountry();
199       }
200     }
201   }
202  
203   /**
204    * Derive parent config (up the package hierarchy) from resource and locale.
205    *
206    * @param resource The resource name.
207    * @param locale The locale.
208    * @return The parent config.
209    */
210   static Config deriveParentConfig(String resource, Locale locale) {
2113525    if (resource.equals(PACKAGE_RESOURCE)) {
212534      return TERMINAL_CONFIG;
2132991    } else if (resource.endsWith(PACKAGE_RESOURCE)) {
2142275      String parentResource = deriveParentResourceName(resource);
2152275      return getConfig(parentResource, locale);
216     } else {
217716      String parentResource = stripLastName(resource) + PACKAGE_RESOURCE;
218716      return getConfig(parentResource, locale);
219     }
220   }
221  
222   /**
223    * Derive config for parent locale from resource and locale.
224    *
225    * @param resource The resource name.
226    * @param locale The locale.
227    * @return The config for parent locale.
228    */
229   static Config deriveParentLocale(String resource, Locale locale) {
2303542    if (locale == null) {
2312542      return TERMINAL_CONFIG;
2321000    } else if (locale.getCountry().equals(EMPTY_STRING)) {
233556      return getConfig(resource, null);
234444    } else if (locale.getVariant().equals(EMPTY_STRING)) {
235444      locale = new Locale(locale.getLanguage(), EMPTY_STRING);
236444      return getConfig(resource, locale);
237     } else {
2380      locale = new Locale(locale.getLanguage(), locale.getVariant());
2390      return getConfig(resource, locale);
240     }
241   }
242  
243   /**
244    * Derive parent resource name for package resource (by chopping off the
245    * last value).
246    *
247    * @param resource The resource name.
248    * @return The parent resource name.
249    */
250   static String deriveParentResourceName(String resource) {
2512275    int length = resource.length() - PACKAGE_RESOURCE_LENGTH - 1;
2522275    String stripped = resource.substring(0, length);
2532275    return stripLastName(stripped) + PACKAGE_RESOURCE;
254   }
255  
256   //-----------------------------------------------------------------
257   // Static initialization methods
258   //-----------------------------------------------------------------
259  
260   /**
261    * Load config data for resource.
262    *
263    * @param resource The resource name.
264    * @return The resource data.
265    */
266   static Map initData(String resource) {
2673544    Map data = new SequencedHashMap();
2683544    data.putAll(loadPropertyFileData(resource));
2693544    loadXMLFileData(resource, data);
2703544    loadExtraXMLData(resource, data);
2713544    trimWhitespace(data);
2723544    expandProperties(resource, data);
2733542    if (data.isEmpty()) {
2742976      return Collections.EMPTY_MAP; // Singleton to reduce memory usage
275     } else {
276566      return data;
277     }
278   }
279  
280   /**
281    * Load config data from property file and inserts it into the map.
282    *
283    * @param resource The resource name.
284    * @return The resource data.
285    */
286   static Map loadPropertyFileData(String resource) {
2873544    String file = deriveFile(resource, PROPERTY_FILE_EXTENSION);
2883544    InputStream is = getStream(file);
2893544    if (is != null) {
290386      Properties props = new Properties();
291       try {
292386        props.load(is);
293386      } catch (IOException ex) {
2940        handleFileError(file, ex);
2950      } finally {
2960        try {
297386          is.close();
298386        } catch (IOException ex) {
2990          handleFileError(file, ex);
300386        }
301       }
302386      return props;
303     } else {
3043158      return Collections.EMPTY_MAP;
305     }
306   }
307  
308   /**
309    * Load config data from xml file and inserts it into the map.
310    *
311    * @param resource The resource name.
312    * @param data The map to which resource data is added.
313    */
314   static void loadXMLFileData(String resource, Map data) {
3153544    String file = deriveFile(resource, XML_FILE_EXTENSION);
3163544    InputStream is = getStream(file);
3173544    if (is != null) {
318       try {
319186        SAXParser sp = SAXParserFactory.newInstance().newSAXParser();
320186        XMLDigester handler = new XMLDigester();
321186        sp.parse(is, handler);
322186        data.putAll(handler.getData());
323186      } catch (Exception ex) {
3240        handleFileError(file, ex);
3250      } finally {
3260        try {
327186          is.close();
328186        } catch (IOException ex) {
3290          handleFileError(file, ex);
330186        }
331       }
332     }
3333544  }
334  
335   /**
336    * Load extra xml data from special location.
337    *
338    * @param resource The resource name.
339    * @param data The map to which resource data is added.
340    */
341   static void loadExtraXMLData(String resource, Map data) {
3423544    String file = (String) data.get(EXTRA_XML_CONFIG);
3433544    if (file == null) {
3443528      return;
345     }
34616    InputStream is = getStream(file);
34716    if (is != null) {
348       try {
34916        SAXParser sp = SAXParserFactory.newInstance().newSAXParser();
35016        XMLDigester handler = new XMLDigester();
35116        sp.parse(is, handler);
35216        data.putAll(handler.getData());
35316      } catch (Exception ex) {
3540        handleFileError(file, ex);
3550      } finally {
3560        try {
35716          is.close();
35816        } catch (IOException ex) {
3590          handleFileError(file, ex);
36016        }
361       }
362     }
36316  }
364  
365   /**
366    * Derive file path from resource name by replace key delimiters
367    * with path delimiters and adding the file extension.
368    *
369    * @param resource The resource name.
370    * @param extension The file extension.
371    * @return The derived file path.
372    */
373   static String deriveFile(String resource, String extension) {
3747088    return resource.replace(KEY_DELIMITER, '/') + extension;
375   }
376  
377   /**
378    * Open input stream to file searching the classpath.
379    *
380    * @param file The relative file path.
381    * @return A stream pointing the to file (or <code>null</code> if not found).
382    */
383   static InputStream getStream(String file) {
3847104    ClassLoader loader = Thread.currentThread().getContextClassLoader();
3857104    return loader.getResourceAsStream(file);
386   }
387  
388   /**
389    * Report file error.
390    *
391    * @param file The relative file path.
392    * @param ex The original exception.
393    * @throws ConfigurationException With information about the error.
394    */
395   static void handleFileError(String file, Exception ex)
396     throws ConfigurationException {
3970    throw new ConfigurationException(
398       ConfigurationException.MESSAGE_CONFIG_FILE_ERROR,
399       new Object[] { file, ex.getMessage()});
400   }
401  
402   /**
403    * Trim whitespace from data.
404    *
405    * @param data The data.
406    */
407   static void trimWhitespace(Map data) {
4083544    Object[] keys = data.keySet().toArray();
40911629    for (int i = 0; i < keys.length; i++) {
4108085      Object key = keys[i];
4118085      String value = (String) data.get(key);
4128085      data.put(key, value.trim());
413     }
4143544  }
415  
416   /**
417    * Expand all property values.
418    *
419    * @param resource The resource name.
420    * @param data The map containing the data.
421    * @throws ConfigurationException If there is an expansion overflow.
422    */
423   static void expandProperties(String resource, Map data)
424     throws ConfigurationException {
425     // Use key array to avoid ConcurrentModificationException
4263544    Object[] keys = data.keySet().toArray();
42711627    for (int i = 0; i < keys.length; i++) {
4288085      Object key = keys[i];
429       try {
4308085        String oldValue = (String) data.get(key);
4318085        String newValue = expandProperty(oldValue, data, 0);
4328083        if (newValue != oldValue) {
43312          data.put(key, newValue);
434         }
4358083      } catch (StackOverflowError e) {
4362        throw new ConfigurationException(
437           ConfigurationException.MESSAGE_PROPERTY_EXPANSION_OVERFLOW,
438           new Object[] { resource, key });
439       }
440     }
4413542  }
442  
443   /**
444    * Expand single property value.
445    *
446    * @param value The property value being expanded.
447    * @param data The map containing the data.
448    * @param counter A running count of the expansion recursion depth.
449    * @return The expanded property value.
450    * @throws StackOverflowError If the expansion limit is exceeded.
451    */
452   static String expandProperty(String value, Map data, int counter)
453     throws StackOverflowError {
45417878    if (counter > EXPANSION_LIMIT) {
4550      throw new StackOverflowError();
456     }
457  
45817878    int start = value.indexOf(EXPANSION_START);
45917878    if (start < 0) {
4608083      return value;
461     }
4629795    int end = value.indexOf(EXPANSION_END, start);
4639795    if (end < 0) {
4640      return value;
465     }
466  
4679795    String prefix = value.substring(0, start);
4689795    String suffix = value.substring(end + 1);
4699795    String key = value.substring(start + 2, end);
470  
4719795    if (key.equals(FILE_SEPARATOR_ABBREVIATION)) {
4724      key = FILE_SEPARATOR;
4739791    } else if (key.equals(PATH_SEPARATOR_ABBREVIATION)) {
4740      key = PATH_SEPARATOR;
475     }
476  
4779795    String property = (String) data.get(key);
4789795    if (property == null) {
4798      property = System.getProperty(key);
480     }
481  
4829795    value = prefix + property + suffix;
483  
4849793    return expandProperty(value, data, counter++);
485   }
486  
487   /**
488    * Strip last value from name.
489    *
490    * @param name The name being stripped.
491    * @return The stripped name.
492    */
493   static String stripLastName(String name) {
49412576    return name.substring(0, name.lastIndexOf(KEY_DELIMITER) + 1);
495   }
496  
497   /**
498    * Strip last value and period from name.
499    *
500    * @param name The name being stripped.
501    * @return The stripped name.
502    */
503   static String stripLastNameAndPeriod(String name) {
50427768    if (name.indexOf(KEY_DELIMITER) < 0) {
5059261      return EMPTY_STRING;
506     }
50718507    return name.substring(0, name.lastIndexOf(KEY_DELIMITER));
508   }
509  
510   /** Singleton that terminates property search by returning null. */
5111  static final Config TERMINAL_CONFIG = new ConfigImpl() {
512     String getWithContext(String context, String key) {
513       return null;
514     }
515  
516     Map getMapWithContext(String context, String key) {
517       return Collections.EMPTY_MAP;
518     }
519  
520     public String getRawValue(String key) {
521       return null;
522     }
523  
524     public Map getOrderedMap(String key) {
525       return Collections.EMPTY_MAP;
526     }
527   };
528 }

this report was generated by version 1.0.5 of jcoverage.
visit www.jcoverage.com for updates.

copyright © 2003, jcoverage ltd. all rights reserved.
Java is a trademark of Sun Microsystems, Inc. in the United States and other countries.