View Javadoc
1   /*
2    * Copyright 2007 Ralf Joachim
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5    * in compliance with the License. You may obtain a copy of the License at
6    *
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software distributed under the License
10   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11   * or implied. See the License for the specific language governing permissions and limitations under
12   * the License.
13   *
14   * $Id: Configuration.java 6907 2007-03-28 21:24:52Z rjoachim $
15   */
16  package org.castor.core.util;
17  
18  import java.io.File;
19  import java.io.FileInputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.net.URL;
23  import java.text.MessageFormat;
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Properties;
29  
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  /**
35   * Abstract base class to hold Castor configuration properties.
36   * 
37   * @version $Id: Configuration.java,v 1.8 2006/03/08 17:25:52 jens Exp $
38   * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a>
39   * @since 1.1.3
40   */
41  public abstract class AbstractProperties {
42  
43    /**
44     * Name of the system property that can be used to specify the location of user properties.
45     */
46    private static final String USER_PROPERTIES_SYSTEM_PROPERTY =
47        "org.castor.user.properties.location";
48  
49    /**
50     * The <a href="http://jakarta.apache.org/commons/logging/">Jakarta Commons Logging</a> instance
51     * used for all logging.
52     */
53    private static final Log LOG = LogFactory.getLog(AbstractProperties.class);
54  
55    /**
56     * {@link ClassLoader} to be used for all classes of Castor and its required libraries.
57     */
58    private final ClassLoader _applicationClassLoader;
59  
60    /**
61     * {@link ClassLoader} to be used for all domain objects that are marshalled/unmarshalled or
62     * loaded from the database.
63     */
64    private final ClassLoader _domainClassLoader;
65  
66    /**
67     * Parent properties.
68     */
69    private final AbstractProperties _parent;
70  
71    private final Map<Object, Object> _map = new HashMap<>();
72  
73    /**
74     * Default constructor. Application and domain class loaders will be initialized to the one used
75     * to load the concrete properties class. No parent properties will be set.
76     */
77    protected AbstractProperties() {
78      this(null, null);
79    }
80  
81    /**
82     * Construct properties that uses the specified class loaders. No parent properties will be set.
83     * 
84     * @param app Classloader to be used for all classes of Castor and its required libraries.
85     * @param domain Classloader to be used for all domain objects.
86     */
87    protected AbstractProperties(final ClassLoader app, final ClassLoader domain) {
88      _applicationClassLoader = (app != null) ? app : getClass().getClassLoader();
89      _domainClassLoader = (domain != null) ? domain : getClass().getClassLoader();
90  
91      _parent = null;
92    }
93  
94    /**
95     * Construct properties with given parent. Application and domain class loaders will be
96     * initialized to the ones of the parent.
97     * 
98     * @param parent Parent properties.
99     */
100   protected AbstractProperties(final AbstractProperties parent) {
101     _applicationClassLoader = parent.getApplicationClassLoader();
102     _domainClassLoader = parent.getDomainClassLoader();
103 
104     _parent = parent;
105   }
106 
107   /**
108    * Get classloader to be used for all classes of Castor and its required libraries.
109    * 
110    * @return Classloader to be used for all classes of Castor and its required libraries.
111    */
112   public final ClassLoader getApplicationClassLoader() {
113     return _applicationClassLoader;
114   }
115 
116   /**
117    * Get classloader to be used for all domain objects that are marshalled/unmarshalled or loaded
118    * from the database.
119    * 
120    * @return Classloader to be used for all domain objects.
121    */
122   public final ClassLoader getDomainClassLoader() {
123     return _domainClassLoader;
124   }
125 
126   /**
127    * Load module properties from default locations. <br/>
128    * First it loads default properties contained in Castor JAR. This gets overwritten by a
129    * properties found on Java library directory. If no properties could be found until that point a
130    * PropertiesException will be thrown.
131    * 
132    * @param path Path to the default properties to load.
133    * @param filename Name of the properties file.
134    */
135   protected void loadDefaultProperties(final String path, final String filename) {
136     Properties properties = new Properties();
137 
138     // Get default properties from the Castor JAR.
139     boolean inCastorJar = loadFromClassPath(properties, path + filename);
140 
141     // Get overriding properties from the Java library directory, ignore if not
142     // found. If found merge existing properties.
143     boolean inJavaLibDir = loadFromJavaHome(properties, filename);
144 
145     // Couldn't find properties in Castor jar nor Java library directory.
146     if (!inCastorJar && !inJavaLibDir) {
147       throw new PropertiesException("Failed to load properties: " + filename);
148     }
149 
150     _map.putAll(properties);
151   }
152 
153   /**
154    * Load common user properties from classpath root and current working directory. <br/>
155    * First it loads default properties contained in Castor JAR. This gets overwritten by properties
156    * found on Java library directory. If no properties could be found until that point a
157    * PropertiesException will be thrown. At last overriding properties are loaded from root of
158    * classpath or, if that could not be found, from local working directory.
159    * 
160    * @param filename Name of the properties file.
161    */
162   protected void loadUserProperties(final String filename) {
163     Properties properties = new Properties();
164 
165     // Get common properties from the classpath root, ignore if not found.
166     boolean userPropertiesLoaded = loadFromClassPath(properties, "/" + filename);
167 
168     // If not found on classpath root, either it doesn't exist, or "." is not part of
169     // the classpath, try looking at local working directory.
170     if (!userPropertiesLoaded) {
171       userPropertiesLoaded = loadFromWorkingDirectory(properties, filename);
172     }
173 
174     if (!userPropertiesLoaded) {
175       String property = System.getProperty(USER_PROPERTIES_SYSTEM_PROPERTY);
176       if (property != null && property.length() > 0) {
177         File file = new File(property);
178         if (file.exists()) {
179           LOG.info("Loading custom Castor properties from " + file.getAbsolutePath());
180           userPropertiesLoaded = loadFromFile(properties, file);
181         } else {
182           LOG.warn(file.getAbsolutePath() + " is not a valid file.");
183         }
184       }
185     }
186 
187     _map.putAll(properties);
188   }
189 
190   /**
191    * Load properties with given filename from classpath and merge them into the given properties.
192    * 
193    * @param properties Properties to merge the loaded ones into.
194    * @param filename Name of the properties file to load from classpath.
195    * @return <code>true</code> if properties could be loaded, <code>false</code> otherwise.
196    */
197   private boolean loadFromClassPath(final Properties properties, final String filename) {
198     InputStream classPathStream = null;
199     try {
200       URL url = getClass().getResource(filename);
201       if (url != null) {
202         classPathStream = url.openStream();
203         properties.load(classPathStream);
204 
205         if (LOG.isDebugEnabled()) {
206           LOG.debug("Properties loaded from classpath: " + filename);
207         }
208 
209         return true;
210       }
211       return false;
212     } catch (Exception ex) {
213       LOG.warn("Failed to load properties from classpath: " + filename, ex);
214       return false;
215     } finally {
216       if (classPathStream != null) {
217         try {
218           classPathStream.close();
219         } catch (IOException e) {
220           LOG.warn("Failed to close properties from classpath: " + filename);
221         }
222       }
223     }
224   }
225 
226   /**
227    * Load properties with given filename from Java library directory and merge them into the given
228    * properties.
229    * 
230    * @param properties Properties to merge the loaded ones into.
231    * @param filename Name of the properties file to load from Java library directory.
232    * @return <code>true</code> if properties could be loaded, <code>false</code> otherwise.
233    */
234   private boolean loadFromJavaHome(final Properties properties, final String filename) {
235     try {
236       String javaHome = System.getProperty("java.home");
237       if (javaHome == null) {
238         return false;
239       }
240       return loadFromFile(properties, new File(new File(javaHome, "lib"), filename));
241     } catch (SecurityException ex) {
242       LOG.warn("Security policy prevented access to system property 'java.home'.", ex);
243       return false;
244     }
245   }
246 
247   /**
248    * Load properties with given filename from local working directory and merge them into the given
249    * properties.
250    * 
251    * @param properties Properties to merge the loaded ones into.
252    * @param filename Name of the properties file to load from local working directory.
253    * @return <code>true</code> if properties could be loaded, <code>false</code> otherwise.
254    */
255   private boolean loadFromWorkingDirectory(final Properties properties, final String filename) {
256     return loadFromFile(properties, new File(filename));
257   }
258 
259   /**
260    * Load properties with given file and merge them into the given properties.
261    * 
262    * @param properties Properties to merge the loaded ones into.
263    * @param file Properties file to load.
264    * @return <code>true</code> if properties could be loaded, <code>false</code> otherwise.
265    */
266   private boolean loadFromFile(final Properties properties, final File file) {
267     try {
268       if (file.exists() && file.canRead()) {
269         try (InputStream fileStream = new FileInputStream(file)) {
270           properties.load(fileStream);
271         }
272 
273         if (LOG.isDebugEnabled()) {
274           LOG.debug("Properties file loaded: " + file);
275         }
276 
277         return true;
278       }
279       return false;
280     } catch (SecurityException ex) {
281       LOG.warn("Security policy prevented access to properties file: " + file, ex);
282       return false;
283     } catch (Exception ex) {
284       LOG.warn("Failed to load properties file: " + file, ex);
285       return false;
286     }
287   }
288 
289   /**
290    * Put given value associated with given key into the properties map of this properties. If the
291    * properties previously associated the key to another value the previous value will be returned.
292    * If a mapping for the key previously exist in the parent properties only, the method returns
293    * <code>null</code> and not the value of the parent. This allows to distingush if the mapping
294    * existed in this properties or one of its parents. <br/>
295    * Putting a value in this properties does not change the value of its parent but the parents
296    * value isn't visible any more as it gets overwritten by this properties one. While this allows
297    * to redefine the value of a property it isn't allowed to undefine it. Therefore a
298    * <code>NullPointerException</code> will be thrown if the given value is <code>null</code>.
299    * 
300    * @param key Key of the property to put into properties.
301    * @param value Value to put into properties associated with the given key..
302    * @return Object in this properties that previously has been associated with the given key.
303    */
304   public final synchronized Object put(final String key, final Object value) {
305     if (value == null) {
306       throw new NullPointerException();
307     }
308     return _map.put(key, value);
309   }
310 
311   /**
312    * Remove any value previously associated with the given key from this properties. The value
313    * previously associated with the key in this properties will be returned. If a mapping for the
314    * key existed in the parent properties only, the method returns <code>null</code> and not the
315    * value of the parent. This allows to distingush if the mapping existed in this properties or one
316    * of its parents. <br/>
317    * Removing the value from this properties does not mean that consecutive gets return
318    * <code>null</code> as one of the parents may still contain a mapping for the key that was hidden
319    * by the mapping in this properties.
320    * 
321    * @param key Key of the property to remove from properties.
322    * @return Object in this properties that previously has been associated with the given key.
323    */
324   public final synchronized Object remove(final String key) {
325     return _map.remove(key);
326   }
327 
328   /**
329    * Searches for the property with the specified key in this property map. If the key is not found
330    * in this property map, the parent property map, and its parents, recursively, are then checked.
331    * <br/>
332    * If the key maps to any object value, it will be returned as is. If the property is not found,
333    * <code>null</code> will be returned.
334    *
335    * @param key Key of the property to get from properties.
336    * @return Object in this property map with the specified key value.
337    */
338   protected synchronized Object get(final String key) {
339     Object value = _map.get(key);
340     if ((value == null) && (_parent != null)) {
341       value = _parent.get(key);
342     }
343     return value;
344   }
345 
346   /**
347    * Searches for the property with the specified key in this property map. If the key is not found
348    * in this property map, the parent property map, and its parents, recursively, are then checked.
349    * <br/>
350    * If the key maps to a boolean value, it will be returned as is. For string values that are
351    * equal, ignore case, to 'true' or 'false', the respective boolean value will be returned. If the
352    * property is not found, <code>null</code> will be returned. For all other types and string
353    * values a PropertiesException will be thrown. This behaviour is intended for those usecases that
354    * need distinguish between values that are missconfigured or not specified at all.
355    *
356    * @param key Property key.
357    * @return Boolean value in this property map with the specified key value.
358    */
359   public final Boolean getBoolean(final String key) {
360     Object objectValue = get(key);
361 
362     if (objectValue == null) {
363       return null;
364     } else if (objectValue instanceof Boolean) {
365       return (Boolean) objectValue;
366     } else if (objectValue instanceof String) {
367       String stringValue = (String) objectValue;
368       if ("true".equalsIgnoreCase(stringValue)) {
369         return Boolean.TRUE;
370       } else if ("false".equalsIgnoreCase(stringValue)) {
371         return Boolean.FALSE;
372       }
373     }
374 
375     Object[] args = new Object[] {key, objectValue};
376     String msg = "Properties value can not be converted to boolean: {0}={1}";
377     throw new PropertiesException(MessageFormat.format(msg, args));
378   }
379 
380   /**
381    * Searches for the property with the specified key in this property map. If the key is not found
382    * in this property map, the parent property map, and its parents, recursively, are then checked.
383    * <br/>
384    * If the key maps to a boolean value, it will be returned as is. For string values that are
385    * equal, ignore case, to 'true' or 'false', the respective boolean value will be returned. In all
386    * other cases the given default value will be returned.
387    *
388    * @param key Property key.
389    * @param defaultValue Default value.
390    * @return Boolean value in this property map with the specified key value.
391    */
392   public final boolean getBoolean(final String key, final boolean defaultValue) {
393     Object objectValue = get(key);
394 
395     if (objectValue instanceof Boolean) {
396       return ((Boolean) objectValue).booleanValue();
397     } else if (objectValue instanceof String) {
398       String stringValue = (String) objectValue;
399       if ("true".equalsIgnoreCase(stringValue)) {
400         return true;
401       } else if ("false".equalsIgnoreCase(stringValue)) {
402         return false;
403       }
404     }
405 
406     return defaultValue;
407   }
408 
409   /**
410    * Searches for the property with the specified key in this property map. If the key is not found
411    * in this property map, the parent property map, and its parents, recursively, are then checked.
412    * <br/>
413    * If the key maps to a integer value, it will be returned as is. For string values that can be
414    * interpreted as signed decimal integer, the respective integer value will be returned. If the
415    * property is not found, <code>null</code> will be returned. For all other types and string
416    * values a PropertiesException will be thrown. This behaviour is intended for those usecases that
417    * need distinguish between values that are missconfigured or not specified at all.
418    *
419    * @param key Property key.
420    * @return Integer value in this property map with the specified key value.
421    */
422   public final Integer getInteger(final String key) {
423     Object objectValue = get(key);
424 
425     if (objectValue == null) {
426       return null;
427     } else if (objectValue instanceof Integer) {
428       return (Integer) objectValue;
429     } else if (objectValue instanceof String) {
430       try {
431         return Integer.valueOf((String) objectValue);
432       } catch (NumberFormatException ex) {
433         Object[] args = new Object[] {key, objectValue};
434         String msg = "Properties value can not be converted to int: {0}={1}";
435         throw new PropertiesException(MessageFormat.format(msg, args), ex);
436       }
437     }
438 
439     Object[] args = new Object[] {key, objectValue};
440     String msg = "Properties value can not be converted to int: {0}={1}";
441     throw new PropertiesException(MessageFormat.format(msg, args));
442   }
443 
444   /**
445    * Searches for the property with the specified key in this property map. If the key is not found
446    * in this property map, the parent property map, and its parents, recursively, are then checked.
447    * <br/>
448    * If the key maps to a integer value, it will be returned as is. For string values that can be
449    * interpreted as signed decimal integer, the respective integer value will be returned. In all
450    * other cases the given default value will be returned.
451    *
452    * @param key Property key.
453    * @param defaultValue Default value.
454    * @return Integer value in this property map with the specified key value.
455    */
456   public final int getInteger(final String key, final int defaultValue) {
457     Object objectValue = get(key);
458 
459     if (objectValue instanceof Integer) {
460       return ((Integer) objectValue).intValue();
461     } else if (objectValue instanceof String) {
462       String stringValue = (String) objectValue;
463       try {
464         return Integer.parseInt(stringValue);
465       } catch (NumberFormatException ex) {
466         return defaultValue;
467       }
468     }
469 
470     return defaultValue;
471   }
472 
473   /**
474    * Searches for the property with the specified key in this property map. If the key is not found
475    * in this property map, the parent property map, and its parents, recursively, are then checked.
476    * <br/>
477    * If the key maps to a string value, it will be returned as is. If the property is not found,
478    * <code>null</code> will be returned. For all other types a PropertiesException will be thrown.
479    *
480    * @param key Property key.
481    * @return String value in this property map with the specified key value.
482    */
483   public final String getString(final String key) {
484     Object objectValue = get(key);
485 
486     if (objectValue == null) {
487       return null;
488     } else if (objectValue instanceof String) {
489       return (String) objectValue;
490     }
491 
492     Object[] args = new Object[] {key, objectValue};
493     String msg = "Properties value is not a string: {0}={1}";
494     throw new PropertiesException(MessageFormat.format(msg, args));
495   }
496 
497   /**
498    * Searches for the property with the specified key in this property map. If the key is not found
499    * in this property map, the parent property map, and its parents, recursively, are then checked.
500    * <br/>
501    * If the key maps to a string value that is not empty, it will be returned as is. In all other
502    * cases the given default value will be returned.
503    *
504    * @param key Property key.
505    * @param defaultValue Default value.
506    * @return String value in this property map with the specified key value.
507    */
508   public final String getString(final String key, final String defaultValue) {
509     Object objectValue = get(key);
510 
511     if ((objectValue instanceof String) && !"".equals(objectValue)) {
512       return (String) objectValue;
513     }
514 
515     return defaultValue;
516   }
517 
518   /**
519    * Searches for the property with the specified key in this property map. If the key is not found
520    * in this property map, the parent property map, and its parents, recursively, are then checked.
521    * <br/>
522    * If the key maps to a string array, it will be returned as is. A simple string will be converted
523    * into a string array by splitting it into substrings at every occurence of ',' character. If the
524    * property is not found, <code>null</code> will be returned. For all other types a
525    * PropertiesException will be thrown.
526    *
527    * @param key Property key.
528    * @return String array in this property map with the specified key value.
529    */
530   public final String[] getStringArray(final String key) {
531     Object objectValue = get(key);
532 
533     if (objectValue == null) {
534       return null;
535     } else if (objectValue instanceof String[]) {
536       return (String[]) objectValue;
537     } else if (objectValue instanceof String) {
538       return StringUtils.split((String) objectValue, ',');
539     }
540 
541     Object[] args = new Object[] {key, objectValue};
542     String msg = "Properties value is not a String[]: {0}={1}";
543     throw new PropertiesException(MessageFormat.format(msg, args));
544   }
545 
546   /**
547    * Searches for the property with the specified key in this property map. If the key is not found
548    * in this property map, the parent property map, and its parents, recursively, are then checked.
549    * <br/>
550    * If the key maps to a class, it will be returned as is. A simple string will be interpreted as
551    * class name of which the class will be loaded with the given class loader. If the property is
552    * not found, <code>null</code> will be returned. For all other types and if loading of the class
553    * fails a PropertiesException will be thrown.
554    *
555    * @param key Property key.
556    * @param loader Class loader to load classes with.
557    * @return Class in this property map with the specified key value.
558    */
559   public final Class getClass(final String key, final ClassLoader loader) {
560     Object objectValue = get(key);
561 
562     if (objectValue == null) {
563       return null;
564     } else if (objectValue instanceof Class) {
565       return (Class) objectValue;
566     } else if (objectValue instanceof String) {
567       String classname = (String) objectValue;
568       try {
569         return loader.loadClass(classname);
570       } catch (ClassNotFoundException ex) {
571         Object[] args = new Object[] {key, classname};
572         String msg = "Could not find class of properties value: {0}={1}";
573         throw new PropertiesException(MessageFormat.format(msg, args), ex);
574       }
575     }
576 
577     Object[] args = new Object[] {key, objectValue};
578     String msg = "Properties value is not a Class: {0}={1}";
579     throw new PropertiesException(MessageFormat.format(msg, args));
580   }
581 
582   /**
583    * Searches for the property with the specified key in this property map. If the key is not found
584    * in this property map, the parent property map, and its parents, recursively, are then checked.
585    * <br/>
586    * If the key maps to a class array, it will be returned as is. A simple string will be splitted
587    * it into substrings at every occurence of ',' character. Each of these substrings will
588    * interpreted as class name of which the class will be loaded with the given class loader. If the
589    * property is not found, <code>null</code> will be returned. For all other types and if loading
590    * of one of the classes fails a PropertiesException will be thrown.
591    *
592    * @param key Property key.
593    * @param loader Class loader to load classes with.
594    * @return Class array in this property map with the specified key value.
595    */
596   public final Class[] getClassArray(final String key, final ClassLoader loader) {
597     Object objectValue = get(key);
598 
599     if (objectValue == null) {
600       return null;
601     } else if (objectValue instanceof Class[]) {
602       return (Class[]) objectValue;
603     } else if (objectValue instanceof String) {
604       String[] classnames = StringUtils.split((String) objectValue, ',');
605       Class[] classes = new Class[classnames.length];
606       for (int i = 0; i < classnames.length; i++) {
607         try {
608           classes[i] = loader.loadClass(classnames[i]);
609         } catch (ClassNotFoundException ex) {
610           Object[] args = new Object[] {key, Integer.valueOf(i), classnames[i]};
611           String msg = "Could not find class of properties value: {0}[{1}]={2}";
612           throw new PropertiesException(MessageFormat.format(msg, args), ex);
613         }
614       }
615       return classes;
616     }
617 
618     Object[] args = new Object[] {key, objectValue};
619     String msg = "Properties value is not a Class[]: {0}={1}";
620     throw new PropertiesException(MessageFormat.format(msg, args));
621   }
622 
623   /**
624    * Searches for the property with the specified key in this property map. If the key is not found
625    * in this property map, the parent property map, and its parents, recursively, are then checked.
626    * <br/>
627    * If the key maps to any object value, it will be returned as is. If the property is not found,
628    * <code>null</code> will be returned.
629    *
630    * @param key Property key.
631    * @return Object in this property map with the specified key value.
632    */
633   public final Object getObject(final String key) {
634     return get(key);
635   }
636 
637   /**
638    * Searches for the property with the specified key in this property map. If the key is not found
639    * in this property map, the parent property map, and its parents, recursively, are then checked.
640    * <br/>
641    * If the key maps to a object array, it will be returned as is. A simple string will be splitted
642    * it into substrings at every occurence of ',' character. Each of these substrings will
643    * interpreted as class name of which the class will be loaded with the given class loader and
644    * instantiated using its default constructor. If the property is not found, <code>null</code>
645    * will be returned. For all other types and if loading or instantiation of one of the classes
646    * fails a PropertiesException will be thrown.
647    *
648    * @param key Property key.
649    * @param loader Class loader to load classes with.
650    * @return Class array in this property map with the specified key value.
651    */
652   public final Object[] getObjectArray(final String key, final ClassLoader loader) {
653     Object objectValue = get(key);
654 
655     if (objectValue == null) {
656       return null;
657     } else if (objectValue instanceof Object[]) {
658       return (Object[]) objectValue;
659     } else if (objectValue instanceof String) {
660       List<Object> objects = new ArrayList<>();
661       String[] classnames = StringUtils.split((String) objectValue, ',');
662       for (int i = 0; i < classnames.length; i++) {
663         String classname = classnames[i];
664         try {
665           if ((classname != null) && !"".equals(classname.trim())) {
666             classname = classname.trim();
667             objects.add(loader.loadClass(classname).newInstance());
668           }
669         } catch (ClassNotFoundException ex) {
670           Object[] args = new Object[] {key, Integer.valueOf(i), classname};
671           String msg = "Could not find configured class: {0}[{1}]={2}";
672           throw new PropertiesException(MessageFormat.format(msg, args), ex);
673         } catch (IllegalAccessException ex) {
674           Object[] args = new Object[] {key, Integer.valueOf(i), classname};
675           String msg = "Could not instantiate configured class: {0}[{1}]={2}";
676           throw new PropertiesException(MessageFormat.format(msg, args), ex);
677         } catch (InstantiationException ex) {
678           Object[] args = new Object[] {key, Integer.valueOf(i), classname};
679           String msg = "Could not instantiate configured class: {0}[{1}]={2}";
680           throw new PropertiesException(MessageFormat.format(msg, args), ex);
681         } catch (ExceptionInInitializerError ex) {
682           Object[] args = new Object[] {key, Integer.valueOf(i), classname};
683           String msg = "Could not instantiate configured class: {0}[{1}]={2}";
684           throw new PropertiesException(MessageFormat.format(msg, args), ex);
685         } catch (SecurityException ex) {
686           Object[] args = new Object[] {key, Integer.valueOf(i), classname};
687           String msg = "Could not instantiate configured class: {0}[{1}]={2}";
688           throw new PropertiesException(MessageFormat.format(msg, args), ex);
689         }
690       }
691       return objects.toArray();
692     }
693 
694     Object[] args = new Object[] {key, objectValue};
695     String msg = "Properties value is not an Object[]: {0}={1}";
696     throw new PropertiesException(MessageFormat.format(msg, args));
697   }
698 
699 }