2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
18
package org.apache.commons.configuration;
21
import java.io.PrintStream;
22
import java.io.PrintWriter;
23
import java.io.StringWriter;
24
import java.lang.reflect.InvocationTargetException;
25
import java.lang.reflect.Method;
26
import java.net.MalformedURLException;
28
import java.util.Iterator;
30
import org.apache.commons.configuration.event.ConfigurationErrorEvent;
31
import org.apache.commons.configuration.event.ConfigurationErrorListener;
32
import org.apache.commons.configuration.event.EventSource;
33
import org.apache.commons.configuration.reloading.Reloadable;
34
import org.apache.commons.configuration.tree.ExpressionEngine;
35
import org.apache.commons.lang.StringUtils;
36
import org.apache.commons.logging.Log;
37
import org.apache.commons.logging.LogFactory;
40
* Miscellaneous utility methods for configurations.
42
* @see ConfigurationConverter Utility methods to convert configurations.
44
* @author <a href="mailto:herve.quiroz@esil.univ-mrs.fr">Herve Quiroz</a>
45
* @author Emmanuel Bourg
46
* @version $Id: ConfigurationUtils.java 1208795 2011-11-30 21:18:17Z oheger $
48
public final class ConfigurationUtils
50
/** Constant for the file URL protocol.*/
51
static final String PROTOCOL_FILE = "file";
53
/** Constant for the resource path separator.*/
54
static final String RESOURCE_PATH_SEPARATOR = "/";
56
/** Constant for the file URL protocol */
57
private static final String FILE_SCHEME = "file:";
59
/** Constant for the name of the clone() method.*/
60
private static final String METHOD_CLONE = "clone";
62
/** Constant for parsing numbers in hex format. */
63
private static final int HEX = 16;
66
private static final Log LOG = LogFactory.getLog(ConfigurationUtils.class);
69
* Private constructor. Prevents instances from being created.
71
private ConfigurationUtils()
73
// to prevent instantiation...
77
* Dump the configuration key/value mappings to some ouput stream.
79
* @param configuration the configuration
80
* @param out the output stream to dump the configuration to
82
public static void dump(Configuration configuration, PrintStream out)
84
dump(configuration, new PrintWriter(out));
88
* Dump the configuration key/value mappings to some writer.
90
* @param configuration the configuration
91
* @param out the writer to dump the configuration to
93
public static void dump(Configuration configuration, PrintWriter out)
95
for (Iterator<String> keys = configuration.getKeys(); keys.hasNext();)
97
String key = keys.next();
98
Object value = configuration.getProperty(key);
113
* Get a string representation of the key/value mappings of a
116
* @param configuration the configuration
117
* @return a string representation of the configuration
119
public static String toString(Configuration configuration)
121
StringWriter writer = new StringWriter();
122
dump(configuration, new PrintWriter(writer));
123
return writer.toString();
127
* <p>Copy all properties from the source configuration to the target
128
* configuration. Properties in the target configuration are replaced with
129
* the properties with the same key in the source configuration.</p>
130
* <p><em>Note:</em> This method is not able to handle some specifics of
131
* configurations derived from {@code AbstractConfiguration} (e.g.
132
* list delimiters). For a full support of all of these features the
133
* {@code copy()} method of {@code AbstractConfiguration} should
134
* be used. In a future release this method might become deprecated.</p>
136
* @param source the source configuration
137
* @param target the target configuration
140
public static void copy(Configuration source, Configuration target)
142
for (Iterator<String> keys = source.getKeys(); keys.hasNext();)
144
String key = keys.next();
145
target.setProperty(key, source.getProperty(key));
150
* <p>Append all properties from the source configuration to the target
151
* configuration. Properties in the source configuration are appended to
152
* the properties with the same key in the target configuration.</p>
153
* <p><em>Note:</em> This method is not able to handle some specifics of
154
* configurations derived from {@code AbstractConfiguration} (e.g.
155
* list delimiters). For a full support of all of these features the
156
* {@code copy()} method of {@code AbstractConfiguration} should
157
* be used. In a future release this method might become deprecated.</p>
159
* @param source the source configuration
160
* @param target the target configuration
163
public static void append(Configuration source, Configuration target)
165
for (Iterator<String> keys = source.getKeys(); keys.hasNext();)
167
String key = keys.next();
168
target.addProperty(key, source.getProperty(key));
173
* Converts the passed in configuration to a hierarchical one. If the
174
* configuration is already hierarchical, it is directly returned. Otherwise
175
* all properties are copied into a new hierarchical configuration.
177
* @param conf the configuration to convert
178
* @return the new hierarchical configuration (the result is <b>null</b> if
179
* and only if the passed in configuration is <b>null</b>)
182
public static HierarchicalConfiguration convertToHierarchical(
185
return convertToHierarchical(conf, null);
189
* Converts the passed in {@code Configuration} object to a
190
* hierarchical one using the specified {@code ExpressionEngine}. This
191
* conversion works by adding the keys found in the configuration to a newly
192
* created hierarchical configuration. When adding new keys to a
193
* hierarchical configuration the keys are interpreted by its
194
* {@code ExpressionEngine}. If they contain special characters (e.g.
195
* brackets) that are treated in a special way by the default expression
196
* engine, it may be necessary using a specific engine that can deal with
197
* such characters. Otherwise <b>null</b> can be passed in for the
198
* {@code ExpressionEngine}; then the default expression engine is
199
* used. If the passed in configuration is already hierarchical, it is
200
* directly returned. (However, the {@code ExpressionEngine} is set if
201
* it is not <b>null</b>.) Otherwise all properties are copied into a new
202
* hierarchical configuration.
204
* @param conf the configuration to convert
205
* @param engine the {@code ExpressionEngine} for the hierarchical
206
* configuration or <b>null</b> for the default
207
* @return the new hierarchical configuration (the result is <b>null</b> if
208
* and only if the passed in configuration is <b>null</b>)
211
public static HierarchicalConfiguration convertToHierarchical(
212
Configuration conf, ExpressionEngine engine)
219
if (conf instanceof HierarchicalConfiguration)
221
HierarchicalConfiguration hc;
222
if (conf instanceof Reloadable)
224
Object lock = ((Reloadable) conf).getReloadLock();
227
hc = new HierarchicalConfiguration((HierarchicalConfiguration) conf);
232
hc = (HierarchicalConfiguration) conf;
236
hc.setExpressionEngine(engine);
243
HierarchicalConfiguration hc = new HierarchicalConfiguration();
246
hc.setExpressionEngine(engine);
249
// Workaround for problem with copy()
250
boolean delimiterParsingStatus = hc.isDelimiterParsingDisabled();
251
hc.setDelimiterParsingDisabled(true);
253
hc.setDelimiterParsingDisabled(delimiterParsingStatus);
259
* Clones the given configuration object if this is possible. If the passed
260
* in configuration object implements the {@code Cloneable}
261
* interface, its {@code clone()} method will be invoked. Otherwise
262
* an exception will be thrown.
264
* @param config the configuration object to be cloned (can be <b>null</b>)
265
* @return the cloned configuration (<b>null</b> if the argument was
267
* @throws ConfigurationRuntimeException if cloning is not supported for
271
public static Configuration cloneConfiguration(Configuration config)
272
throws ConfigurationRuntimeException
282
return (Configuration) clone(config);
284
catch (CloneNotSupportedException cnex)
286
throw new ConfigurationRuntimeException(cnex);
292
* An internally used helper method for cloning objects. This implementation
293
* is not very sophisticated nor efficient. Maybe it can be replaced by an
294
* implementation from Commons Lang later. The method checks whether the
295
* passed in object implements the {@code Cloneable} interface. If
296
* this is the case, the {@code clone()} method is invoked by
297
* reflection. Errors that occur during the cloning process are re-thrown as
298
* runtime exceptions.
300
* @param obj the object to be cloned
301
* @return the cloned object
302
* @throws CloneNotSupportedException if the object cannot be cloned
304
static Object clone(Object obj) throws CloneNotSupportedException
306
if (obj instanceof Cloneable)
310
Method m = obj.getClass().getMethod(METHOD_CLONE);
311
return m.invoke(obj);
313
catch (NoSuchMethodException nmex)
315
throw new CloneNotSupportedException(
316
"No clone() method found for class"
317
+ obj.getClass().getName());
319
catch (IllegalAccessException iaex)
321
throw new ConfigurationRuntimeException(iaex);
323
catch (InvocationTargetException itex)
325
throw new ConfigurationRuntimeException(itex);
330
throw new CloneNotSupportedException(obj.getClass().getName()
331
+ " does not implement Cloneable");
336
* Constructs a URL from a base path and a file name. The file name can
337
* be absolute, relative or a full URL. If necessary the base path URL is
340
* @param basePath the base path URL (can be <b>null</b>)
341
* @param file the file name
342
* @return the resulting URL
343
* @throws MalformedURLException if URLs are invalid
345
public static URL getURL(String basePath, String file) throws MalformedURLException
347
return FileSystem.getDefaultFileSystem().getURL(basePath, file);
351
* Helper method for constructing a file object from a base path and a
352
* file name. This method is called if the base path passed to
353
* {@code getURL()} does not seem to be a valid URL.
355
* @param basePath the base path
356
* @param fileName the file name
357
* @return the resulting file
359
static File constructFile(String basePath, String fileName)
363
File absolute = null;
364
if (fileName != null)
366
absolute = new File(fileName);
369
if (StringUtils.isEmpty(basePath) || (absolute != null && absolute.isAbsolute()))
371
file = new File(fileName);
375
StringBuilder fName = new StringBuilder();
376
fName.append(basePath);
378
// My best friend. Paranoia.
379
if (!basePath.endsWith(File.separator))
381
fName.append(File.separator);
385
// We have a relative path, and we have
386
// two possible forms here. If we have the
387
// "./" form then just strip that off first
388
// before continuing.
390
if (fileName.startsWith("." + File.separator))
392
fName.append(fileName.substring(2));
396
fName.append(fileName);
399
file = new File(fName.toString());
406
* Return the location of the specified resource by searching the user home
407
* directory, the current classpath and the system classpath.
409
* @param name the name of the resource
411
* @return the location of the resource
413
public static URL locate(String name)
415
return locate(null, name);
419
* Return the location of the specified resource by searching the user home
420
* directory, the current classpath and the system classpath.
422
* @param base the base path of the resource
423
* @param name the name of the resource
425
* @return the location of the resource
427
public static URL locate(String base, String name)
429
return locate(FileSystem.getDefaultFileSystem(), base, name);
433
* Return the location of the specified resource by searching the user home
434
* directory, the current classpath and the system classpath.
436
* @param fileSystem the FileSystem to use.
437
* @param base the base path of the resource
438
* @param name the name of the resource
440
* @return the location of the resource
442
public static URL locate(FileSystem fileSystem, String base, String name)
444
if (LOG.isDebugEnabled())
446
StringBuilder buf = new StringBuilder();
447
buf.append("ConfigurationUtils.locate(): base is ").append(base);
448
buf.append(", name is ").append(name);
449
LOG.debug(buf.toString());
454
// undefined, always return null
458
// attempt to create an URL directly
460
URL url = fileSystem.locateFromURL(base, name);
462
// attempt to load from an absolute path
465
File file = new File(name);
466
if (file.isAbsolute() && file.exists()) // already absolute?
471
LOG.debug("Loading configuration from the absolute path " + name);
473
catch (MalformedURLException e)
475
LOG.warn("Could not obtain URL from file", e);
480
// attempt to load from the base directory
485
File file = constructFile(base, name);
486
if (file != null && file.exists())
493
LOG.debug("Loading configuration from the path " + file);
496
catch (MalformedURLException e)
498
LOG.warn("Could not obtain URL from file", e);
502
// attempt to load from the user home directory
507
File file = constructFile(System.getProperty("user.home"), name);
508
if (file != null && file.exists())
515
LOG.debug("Loading configuration from the home path " + file);
519
catch (MalformedURLException e)
521
LOG.warn("Could not obtain URL from file", e);
525
// attempt to load from classpath
528
url = locateFromClasspath(name);
534
* Tries to find a resource with the given name in the classpath.
535
* @param resourceName the name of the resource
536
* @return the URL to the found resource or <b>null</b> if the resource
539
static URL locateFromClasspath(String resourceName)
542
// attempt to load from the context classpath
543
ClassLoader loader = Thread.currentThread().getContextClassLoader();
546
url = loader.getResource(resourceName);
550
LOG.debug("Loading configuration from the context classpath (" + resourceName + ")");
554
// attempt to load from the system classpath
557
url = ClassLoader.getSystemResource(resourceName);
561
LOG.debug("Loading configuration from the system classpath (" + resourceName + ")");
568
* Return the path without the file name, for example http://xyz.net/foo/bar.xml
569
* results in http://xyz.net/foo/
571
* @param url the URL from which to extract the path
572
* @return the path component of the passed in URL
574
static String getBasePath(URL url)
581
String s = url.toString();
582
if (s.startsWith(FILE_SCHEME) && !s.startsWith("file://"))
584
s = "file://" + s.substring(FILE_SCHEME.length());
587
if (s.endsWith("/") || StringUtils.isEmpty(url.getPath()))
593
return s.substring(0, s.lastIndexOf("/") + 1);
598
* Extract the file name from the specified URL.
600
* @param url the URL from which to extract the file name
601
* @return the extracted file name
603
static String getFileName(URL url)
610
String path = url.getPath();
612
if (path.endsWith("/") || StringUtils.isEmpty(path))
618
return path.substring(path.lastIndexOf("/") + 1);
623
* Tries to convert the specified base path and file name into a file object.
624
* This method is called e.g. by the save() methods of file based
625
* configurations. The parameter strings can be relative files, absolute
626
* files and URLs as well. This implementation checks first whether the passed in
627
* file name is absolute. If this is the case, it is returned. Otherwise
628
* further checks are performed whether the base path and file name can be
629
* combined to a valid URL or a valid file name. <em>Note:</em> The test
630
* if the passed in file name is absolute is performed using
631
* {@code java.io.File.isAbsolute()}. If the file name starts with a
632
* slash, this method will return <b>true</b> on Unix, but <b>false</b> on
633
* Windows. So to ensure correct behavior for relative file names on all
634
* platforms you should never let relative paths start with a slash. E.g.
635
* in a configuration definition file do not use something like that:
637
* <properties fileName="/subdir/my.properties"/>
639
* Under Windows this path would be resolved relative to the configuration
640
* definition file. Under Unix this would be treated as an absolute path
643
* @param basePath the base path
644
* @param fileName the file name
645
* @return the file object (<b>null</b> if no file can be obtained)
647
public static File getFile(String basePath, String fileName)
649
// Check if the file name is absolute
650
File f = new File(fileName);
656
// Check if URLs are involved
660
url = new URL(new URL(basePath), fileName);
662
catch (MalformedURLException mex1)
666
url = new URL(fileName);
668
catch (MalformedURLException mex2)
676
return fileFromURL(url);
679
return constructFile(basePath, fileName);
683
* Tries to convert the specified URL to a file object. If this fails,
684
* <b>null</b> is returned. Note: This code has been copied from the
685
* {@code FileUtils} class from <em>Commons IO</em>.
688
* @return the resulting file object
690
public static File fileFromURL(URL url)
692
if (url == null || !url.getProtocol().equals(PROTOCOL_FILE))
698
String filename = url.getFile().replace('/', File.separatorChar);
700
while ((pos = filename.indexOf('%', pos)) >= 0)
702
if (pos + 2 < filename.length())
704
String hexStr = filename.substring(pos + 1, pos + 3);
705
char ch = (char) Integer.parseInt(hexStr, HEX);
706
filename = filename.substring(0, pos) + ch
707
+ filename.substring(pos + 3);
710
return new File(filename);
715
* Convert the specified file into an URL. This method is equivalent
716
* to file.toURI().toURL(). It was used to work around a bug in the JDK
717
* preventing the transformation of a file into an URL if the file name
718
* contains a '#' character. See the issue CONFIGURATION-300 for
719
* more details. Now that we switched to JDK 1.4 we can directly use
720
* file.toURI().toURL().
722
* @param file the file to be converted into an URL
724
static URL toURL(File file) throws MalformedURLException
726
return file.toURI().toURL();
730
* Enables runtime exceptions for the specified configuration object. This
731
* method can be used for configuration implementations that may face errors
732
* on normal property access, e.g. {@code DatabaseConfiguration} or
733
* {@code JNDIConfiguration}. Per default such errors are simply
734
* logged and then ignored. This implementation will register a special
735
* {@link ConfigurationErrorListener} that throws a runtime
736
* exception (namely a {@code ConfigurationRuntimeException}) on
737
* each received error event.
739
* @param src the configuration, for which runtime exceptions are to be
740
* enabled; this configuration must be derived from
741
* {@link EventSource}
743
public static void enableRuntimeExceptions(Configuration src)
745
if (!(src instanceof EventSource))
747
throw new IllegalArgumentException(
748
"Configuration must be derived from EventSource!");
750
((EventSource) src).addErrorListener(new ConfigurationErrorListener()
752
public void configurationError(ConfigurationErrorEvent event)
754
// Throw a runtime exception
755
throw new ConfigurationRuntimeException(event.getCause());