/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.configuration;

import java.io.File;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;

import org.apache.commons.configuration.event.ConfigurationErrorEvent;
import org.apache.commons.configuration.event.ConfigurationErrorListener;
import org.apache.commons.configuration.event.EventSource;
import org.apache.commons.configuration.reloading.Reloadable;
import org.apache.commons.configuration.tree.ExpressionEngine;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

Miscellaneous utility methods for configurations.
Author:Herve Quiroz, Emmanuel Bourg
See Also:
  • Utility methods to convert configurations.
Version:$Id: ConfigurationUtils.java 1208795 2011-11-30 21:18:17Z oheger $
/** * Miscellaneous utility methods for configurations. * * @see ConfigurationConverter Utility methods to convert configurations. * * @author <a href="mailto:herve.quiroz@esil.univ-mrs.fr">Herve Quiroz</a> * @author Emmanuel Bourg * @version $Id: ConfigurationUtils.java 1208795 2011-11-30 21:18:17Z oheger $ */
public final class ConfigurationUtils {
Constant for the file URL protocol.
/** Constant for the file URL protocol.*/
static final String PROTOCOL_FILE = "file";
Constant for the resource path separator.
/** Constant for the resource path separator.*/
static final String RESOURCE_PATH_SEPARATOR = "/";
Constant for the file URL protocol
/** Constant for the file URL protocol */
private static final String FILE_SCHEME = "file:";
Constant for the name of the clone() method.
/** Constant for the name of the clone() method.*/
private static final String METHOD_CLONE = "clone";
Constant for parsing numbers in hex format.
/** Constant for parsing numbers in hex format. */
private static final int HEX = 16;
The logger.
/** The logger.*/
private static final Log LOG = LogFactory.getLog(ConfigurationUtils.class);
Private constructor. Prevents instances from being created.
/** * Private constructor. Prevents instances from being created. */
private ConfigurationUtils() { // to prevent instantiation... }
Dump the configuration key/value mappings to some ouput stream.
Params:
  • configuration – the configuration
  • out – the output stream to dump the configuration to
/** * Dump the configuration key/value mappings to some ouput stream. * * @param configuration the configuration * @param out the output stream to dump the configuration to */
public static void dump(Configuration configuration, PrintStream out) { dump(configuration, new PrintWriter(out)); }
Dump the configuration key/value mappings to some writer.
Params:
  • configuration – the configuration
  • out – the writer to dump the configuration to
/** * Dump the configuration key/value mappings to some writer. * * @param configuration the configuration * @param out the writer to dump the configuration to */
public static void dump(Configuration configuration, PrintWriter out) { for (Iterator<String> keys = configuration.getKeys(); keys.hasNext();) { String key = keys.next(); Object value = configuration.getProperty(key); out.print(key); out.print("="); out.print(value); if (keys.hasNext()) { out.println(); } } out.flush(); }
Get a string representation of the key/value mappings of a configuration.
Params:
  • configuration – the configuration
Returns:a string representation of the configuration
/** * Get a string representation of the key/value mappings of a * configuration. * * @param configuration the configuration * @return a string representation of the configuration */
public static String toString(Configuration configuration) { StringWriter writer = new StringWriter(); dump(configuration, new PrintWriter(writer)); return writer.toString(); }

Copy all properties from the source configuration to the target configuration. Properties in the target configuration are replaced with the properties with the same key in the source configuration.

Note: This method is not able to handle some specifics of configurations derived from AbstractConfiguration (e.g. list delimiters). For a full support of all of these features the copy() method of AbstractConfiguration should be used. In a future release this method might become deprecated.

Params:
  • source – the source configuration
  • target – the target configuration
Since:1.1
/** * <p>Copy all properties from the source configuration to the target * configuration. Properties in the target configuration are replaced with * the properties with the same key in the source configuration.</p> * <p><em>Note:</em> This method is not able to handle some specifics of * configurations derived from {@code AbstractConfiguration} (e.g. * list delimiters). For a full support of all of these features the * {@code copy()} method of {@code AbstractConfiguration} should * be used. In a future release this method might become deprecated.</p> * * @param source the source configuration * @param target the target configuration * @since 1.1 */
public static void copy(Configuration source, Configuration target) { for (Iterator<String> keys = source.getKeys(); keys.hasNext();) { String key = keys.next(); target.setProperty(key, source.getProperty(key)); } }

Append all properties from the source configuration to the target configuration. Properties in the source configuration are appended to the properties with the same key in the target configuration.

Note: This method is not able to handle some specifics of configurations derived from AbstractConfiguration (e.g. list delimiters). For a full support of all of these features the copy() method of AbstractConfiguration should be used. In a future release this method might become deprecated.

Params:
  • source – the source configuration
  • target – the target configuration
Since:1.1
/** * <p>Append all properties from the source configuration to the target * configuration. Properties in the source configuration are appended to * the properties with the same key in the target configuration.</p> * <p><em>Note:</em> This method is not able to handle some specifics of * configurations derived from {@code AbstractConfiguration} (e.g. * list delimiters). For a full support of all of these features the * {@code copy()} method of {@code AbstractConfiguration} should * be used. In a future release this method might become deprecated.</p> * * @param source the source configuration * @param target the target configuration * @since 1.1 */
public static void append(Configuration source, Configuration target) { for (Iterator<String> keys = source.getKeys(); keys.hasNext();) { String key = keys.next(); target.addProperty(key, source.getProperty(key)); } }
Converts the passed in configuration to a hierarchical one. If the configuration is already hierarchical, it is directly returned. Otherwise all properties are copied into a new hierarchical configuration.
Params:
  • conf – the configuration to convert
Returns:the new hierarchical configuration (the result is null if and only if the passed in configuration is null)
Since:1.3
/** * Converts the passed in configuration to a hierarchical one. If the * configuration is already hierarchical, it is directly returned. Otherwise * all properties are copied into a new hierarchical configuration. * * @param conf the configuration to convert * @return the new hierarchical configuration (the result is <b>null</b> if * and only if the passed in configuration is <b>null</b>) * @since 1.3 */
public static HierarchicalConfiguration convertToHierarchical( Configuration conf) { return convertToHierarchical(conf, null); }
Converts the passed in Configuration object to a hierarchical one using the specified ExpressionEngine. This conversion works by adding the keys found in the configuration to a newly created hierarchical configuration. When adding new keys to a hierarchical configuration the keys are interpreted by its ExpressionEngine. If they contain special characters (e.g. brackets) that are treated in a special way by the default expression engine, it may be necessary using a specific engine that can deal with such characters. Otherwise null can be passed in for the ExpressionEngine; then the default expression engine is used. If the passed in configuration is already hierarchical, it is directly returned. (However, the ExpressionEngine is set if it is not null.) Otherwise all properties are copied into a new hierarchical configuration.
Params:
  • conf – the configuration to convert
  • engine – the ExpressionEngine for the hierarchical configuration or null for the default
Returns:the new hierarchical configuration (the result is null if and only if the passed in configuration is null)
Since:1.6
/** * Converts the passed in {@code Configuration} object to a * hierarchical one using the specified {@code ExpressionEngine}. This * conversion works by adding the keys found in the configuration to a newly * created hierarchical configuration. When adding new keys to a * hierarchical configuration the keys are interpreted by its * {@code ExpressionEngine}. If they contain special characters (e.g. * brackets) that are treated in a special way by the default expression * engine, it may be necessary using a specific engine that can deal with * such characters. Otherwise <b>null</b> can be passed in for the * {@code ExpressionEngine}; then the default expression engine is * used. If the passed in configuration is already hierarchical, it is * directly returned. (However, the {@code ExpressionEngine} is set if * it is not <b>null</b>.) Otherwise all properties are copied into a new * hierarchical configuration. * * @param conf the configuration to convert * @param engine the {@code ExpressionEngine} for the hierarchical * configuration or <b>null</b> for the default * @return the new hierarchical configuration (the result is <b>null</b> if * and only if the passed in configuration is <b>null</b>) * @since 1.6 */
public static HierarchicalConfiguration convertToHierarchical( Configuration conf, ExpressionEngine engine) { if (conf == null) { return null; } if (conf instanceof HierarchicalConfiguration) { HierarchicalConfiguration hc; if (conf instanceof Reloadable) { Object lock = ((Reloadable) conf).getReloadLock(); synchronized (lock) { hc = new HierarchicalConfiguration((HierarchicalConfiguration) conf); } } else { hc = (HierarchicalConfiguration) conf; } if (engine != null) { hc.setExpressionEngine(engine); } return hc; } else { HierarchicalConfiguration hc = new HierarchicalConfiguration(); if (engine != null) { hc.setExpressionEngine(engine); } // Workaround for problem with copy() boolean delimiterParsingStatus = hc.isDelimiterParsingDisabled(); hc.setDelimiterParsingDisabled(true); hc.append(conf); hc.setDelimiterParsingDisabled(delimiterParsingStatus); return hc; } }
Clones the given configuration object if this is possible. If the passed in configuration object implements the Cloneable interface, its clone() method will be invoked. Otherwise an exception will be thrown.
Params:
  • config – the configuration object to be cloned (can be null)
Throws:
Returns:the cloned configuration (null if the argument was null, too)
Since:1.3
/** * Clones the given configuration object if this is possible. If the passed * in configuration object implements the {@code Cloneable} * interface, its {@code clone()} method will be invoked. Otherwise * an exception will be thrown. * * @param config the configuration object to be cloned (can be <b>null</b>) * @return the cloned configuration (<b>null</b> if the argument was * <b>null</b>, too) * @throws ConfigurationRuntimeException if cloning is not supported for * this object * @since 1.3 */
public static Configuration cloneConfiguration(Configuration config) throws ConfigurationRuntimeException { if (config == null) { return null; } else { try { return (Configuration) clone(config); } catch (CloneNotSupportedException cnex) { throw new ConfigurationRuntimeException(cnex); } } }
An internally used helper method for cloning objects. This implementation is not very sophisticated nor efficient. Maybe it can be replaced by an implementation from Commons Lang later. The method checks whether the passed in object implements the Cloneable interface. If this is the case, the clone() method is invoked by reflection. Errors that occur during the cloning process are re-thrown as runtime exceptions.
Params:
  • obj – the object to be cloned
Throws:
Returns:the cloned object
/** * An internally used helper method for cloning objects. This implementation * is not very sophisticated nor efficient. Maybe it can be replaced by an * implementation from Commons Lang later. The method checks whether the * passed in object implements the {@code Cloneable} interface. If * this is the case, the {@code clone()} method is invoked by * reflection. Errors that occur during the cloning process are re-thrown as * runtime exceptions. * * @param obj the object to be cloned * @return the cloned object * @throws CloneNotSupportedException if the object cannot be cloned */
static Object clone(Object obj) throws CloneNotSupportedException { if (obj instanceof Cloneable) { try { Method m = obj.getClass().getMethod(METHOD_CLONE); return m.invoke(obj); } catch (NoSuchMethodException nmex) { throw new CloneNotSupportedException( "No clone() method found for class" + obj.getClass().getName()); } catch (IllegalAccessException iaex) { throw new ConfigurationRuntimeException(iaex); } catch (InvocationTargetException itex) { throw new ConfigurationRuntimeException(itex); } } else { throw new CloneNotSupportedException(obj.getClass().getName() + " does not implement Cloneable"); } }
Constructs a URL from a base path and a file name. The file name can be absolute, relative or a full URL. If necessary the base path URL is applied.
Params:
  • basePath – the base path URL (can be null)
  • file – the file name
Throws:
Returns:the resulting URL
/** * Constructs a URL from a base path and a file name. The file name can * be absolute, relative or a full URL. If necessary the base path URL is * applied. * * @param basePath the base path URL (can be <b>null</b>) * @param file the file name * @return the resulting URL * @throws MalformedURLException if URLs are invalid */
public static URL getURL(String basePath, String file) throws MalformedURLException { return FileSystem.getDefaultFileSystem().getURL(basePath, file); }
Helper method for constructing a file object from a base path and a file name. This method is called if the base path passed to getURL() does not seem to be a valid URL.
Params:
  • basePath – the base path
  • fileName – the file name
Returns:the resulting file
/** * Helper method for constructing a file object from a base path and a * file name. This method is called if the base path passed to * {@code getURL()} does not seem to be a valid URL. * * @param basePath the base path * @param fileName the file name * @return the resulting file */
static File constructFile(String basePath, String fileName) { File file; File absolute = null; if (fileName != null) { absolute = new File(fileName); } if (StringUtils.isEmpty(basePath) || (absolute != null && absolute.isAbsolute())) { file = new File(fileName); } else { StringBuilder fName = new StringBuilder(); fName.append(basePath); // My best friend. Paranoia. if (!basePath.endsWith(File.separator)) { fName.append(File.separator); } // // We have a relative path, and we have // two possible forms here. If we have the // "./" form then just strip that off first // before continuing. // if (fileName.startsWith("." + File.separator)) { fName.append(fileName.substring(2)); } else { fName.append(fileName); } file = new File(fName.toString()); } return file; }
Return the location of the specified resource by searching the user home directory, the current classpath and the system classpath.
Params:
  • name – the name of the resource
Returns:the location of the resource
/** * Return the location of the specified resource by searching the user home * directory, the current classpath and the system classpath. * * @param name the name of the resource * * @return the location of the resource */
public static URL locate(String name) { return locate(null, name); }
Return the location of the specified resource by searching the user home directory, the current classpath and the system classpath.
Params:
  • base – the base path of the resource
  • name – the name of the resource
Returns:the location of the resource
/** * Return the location of the specified resource by searching the user home * directory, the current classpath and the system classpath. * * @param base the base path of the resource * @param name the name of the resource * * @return the location of the resource */
public static URL locate(String base, String name) { return locate(FileSystem.getDefaultFileSystem(), base, name); }
Return the location of the specified resource by searching the user home directory, the current classpath and the system classpath.
Params:
  • fileSystem – the FileSystem to use.
  • base – the base path of the resource
  • name – the name of the resource
Returns:the location of the resource
/** * Return the location of the specified resource by searching the user home * directory, the current classpath and the system classpath. * * @param fileSystem the FileSystem to use. * @param base the base path of the resource * @param name the name of the resource * * @return the location of the resource */
public static URL locate(FileSystem fileSystem, String base, String name) { if (LOG.isDebugEnabled()) { StringBuilder buf = new StringBuilder(); buf.append("ConfigurationUtils.locate(): base is ").append(base); buf.append(", name is ").append(name); LOG.debug(buf.toString()); } if (name == null) { // undefined, always return null return null; } // attempt to create an URL directly URL url = fileSystem.locateFromURL(base, name); // attempt to load from an absolute path if (url == null) { File file = new File(name); if (file.isAbsolute() && file.exists()) // already absolute? { try { url = toURL(file); LOG.debug("Loading configuration from the absolute path " + name); } catch (MalformedURLException e) { LOG.warn("Could not obtain URL from file", e); } } } // attempt to load from the base directory if (url == null) { try { File file = constructFile(base, name); if (file != null && file.exists()) { url = toURL(file); } if (url != null) { LOG.debug("Loading configuration from the path " + file); } } catch (MalformedURLException e) { LOG.warn("Could not obtain URL from file", e); } } // attempt to load from the user home directory if (url == null) { try { File file = constructFile(System.getProperty("user.home"), name); if (file != null && file.exists()) { url = toURL(file); } if (url != null) { LOG.debug("Loading configuration from the home path " + file); } } catch (MalformedURLException e) { LOG.warn("Could not obtain URL from file", e); } } // attempt to load from classpath if (url == null) { url = locateFromClasspath(name); } return url; }
Tries to find a resource with the given name in the classpath.
Params:
  • resourceName – the name of the resource
Returns:the URL to the found resource or null if the resource cannot be found
/** * Tries to find a resource with the given name in the classpath. * @param resourceName the name of the resource * @return the URL to the found resource or <b>null</b> if the resource * cannot be found */
static URL locateFromClasspath(String resourceName) { URL url = null; // attempt to load from the context classpath ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (loader != null) { url = loader.getResource(resourceName); if (url != null) { LOG.debug("Loading configuration from the context classpath (" + resourceName + ")"); } } // attempt to load from the system classpath if (url == null) { url = ClassLoader.getSystemResource(resourceName); if (url != null) { LOG.debug("Loading configuration from the system classpath (" + resourceName + ")"); } } return url; }
Return the path without the file name, for example http://xyz.net/foo/bar.xml results in http://xyz.net/foo/
Params:
  • url – the URL from which to extract the path
Returns:the path component of the passed in URL
/** * Return the path without the file name, for example http://xyz.net/foo/bar.xml * results in http://xyz.net/foo/ * * @param url the URL from which to extract the path * @return the path component of the passed in URL */
static String getBasePath(URL url) { if (url == null) { return null; } String s = url.toString(); if (s.startsWith(FILE_SCHEME) && !s.startsWith("file://")) { s = "file://" + s.substring(FILE_SCHEME.length()); } if (s.endsWith("/") || StringUtils.isEmpty(url.getPath())) { return s; } else { return s.substring(0, s.lastIndexOf("/") + 1); } }
Extract the file name from the specified URL.
Params:
  • url – the URL from which to extract the file name
Returns:the extracted file name
/** * Extract the file name from the specified URL. * * @param url the URL from which to extract the file name * @return the extracted file name */
static String getFileName(URL url) { if (url == null) { return null; } String path = url.getPath(); if (path.endsWith("/") || StringUtils.isEmpty(path)) { return null; } else { return path.substring(path.lastIndexOf("/") + 1); } }
Tries to convert the specified base path and file name into a file object. This method is called e.g. by the save() methods of file based configurations. The parameter strings can be relative files, absolute files and URLs as well. This implementation checks first whether the passed in file name is absolute. If this is the case, it is returned. Otherwise further checks are performed whether the base path and file name can be combined to a valid URL or a valid file name. Note: The test if the passed in file name is absolute is performed using java.io.File.isAbsolute(). If the file name starts with a slash, this method will return true on Unix, but false on Windows. So to ensure correct behavior for relative file names on all platforms you should never let relative paths start with a slash. E.g. in a configuration definition file do not use something like that:
<properties fileName="/subdir/my.properties"/>
Under Windows this path would be resolved relative to the configuration definition file. Under Unix this would be treated as an absolute path name.
Params:
  • basePath – the base path
  • fileName – the file name
Returns:the file object (null if no file can be obtained)
/** * Tries to convert the specified base path and file name into a file object. * This method is called e.g. by the save() methods of file based * configurations. The parameter strings can be relative files, absolute * files and URLs as well. This implementation checks first whether the passed in * file name is absolute. If this is the case, it is returned. Otherwise * further checks are performed whether the base path and file name can be * combined to a valid URL or a valid file name. <em>Note:</em> The test * if the passed in file name is absolute is performed using * {@code java.io.File.isAbsolute()}. If the file name starts with a * slash, this method will return <b>true</b> on Unix, but <b>false</b> on * Windows. So to ensure correct behavior for relative file names on all * platforms you should never let relative paths start with a slash. E.g. * in a configuration definition file do not use something like that: * <pre> * &lt;properties fileName="/subdir/my.properties"/&gt; * </pre> * Under Windows this path would be resolved relative to the configuration * definition file. Under Unix this would be treated as an absolute path * name. * * @param basePath the base path * @param fileName the file name * @return the file object (<b>null</b> if no file can be obtained) */
public static File getFile(String basePath, String fileName) { // Check if the file name is absolute File f = new File(fileName); if (f.isAbsolute()) { return f; } // Check if URLs are involved URL url; try { url = new URL(new URL(basePath), fileName); } catch (MalformedURLException mex1) { try { url = new URL(fileName); } catch (MalformedURLException mex2) { url = null; } } if (url != null) { return fileFromURL(url); } return constructFile(basePath, fileName); }
Tries to convert the specified URL to a file object. If this fails, null is returned. Note: This code has been copied from the FileUtils class from Commons IO.
Params:
  • url – the URL
Returns:the resulting file object
/** * Tries to convert the specified URL to a file object. If this fails, * <b>null</b> is returned. Note: This code has been copied from the * {@code FileUtils} class from <em>Commons IO</em>. * * @param url the URL * @return the resulting file object */
public static File fileFromURL(URL url) { if (url == null || !url.getProtocol().equals(PROTOCOL_FILE)) { return null; } else { String filename = url.getFile().replace('/', File.separatorChar); int pos = 0; while ((pos = filename.indexOf('%', pos)) >= 0) { if (pos + 2 < filename.length()) { String hexStr = filename.substring(pos + 1, pos + 3); char ch = (char) Integer.parseInt(hexStr, HEX); filename = filename.substring(0, pos) + ch + filename.substring(pos + 3); } } return new File(filename); } }
Convert the specified file into an URL. This method is equivalent to file.toURI().toURL(). It was used to work around a bug in the JDK preventing the transformation of a file into an URL if the file name contains a '#' character. See the issue CONFIGURATION-300 for more details. Now that we switched to JDK 1.4 we can directly use file.toURI().toURL().
Params:
  • file – the file to be converted into an URL
/** * Convert the specified file into an URL. This method is equivalent * to file.toURI().toURL(). It was used to work around a bug in the JDK * preventing the transformation of a file into an URL if the file name * contains a '#' character. See the issue CONFIGURATION-300 for * more details. Now that we switched to JDK 1.4 we can directly use * file.toURI().toURL(). * * @param file the file to be converted into an URL */
static URL toURL(File file) throws MalformedURLException { return file.toURI().toURL(); }
Enables runtime exceptions for the specified configuration object. This method can be used for configuration implementations that may face errors on normal property access, e.g. DatabaseConfiguration or JNDIConfiguration. Per default such errors are simply logged and then ignored. This implementation will register a special ConfigurationErrorListener that throws a runtime exception (namely a ConfigurationRuntimeException) on each received error event.
Params:
  • src – the configuration, for which runtime exceptions are to be enabled; this configuration must be derived from EventSource
/** * Enables runtime exceptions for the specified configuration object. This * method can be used for configuration implementations that may face errors * on normal property access, e.g. {@code DatabaseConfiguration} or * {@code JNDIConfiguration}. Per default such errors are simply * logged and then ignored. This implementation will register a special * {@link ConfigurationErrorListener} that throws a runtime * exception (namely a {@code ConfigurationRuntimeException}) on * each received error event. * * @param src the configuration, for which runtime exceptions are to be * enabled; this configuration must be derived from * {@link EventSource} */
public static void enableRuntimeExceptions(Configuration src) { if (!(src instanceof EventSource)) { throw new IllegalArgumentException( "Configuration must be derived from EventSource!"); } ((EventSource) src).addErrorListener(new ConfigurationErrorListener() { public void configurationError(ConfigurationErrorEvent event) { // Throw a runtime exception throw new ConfigurationRuntimeException(event.getCause()); } }); } }