    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.configuration2;
    019
    020import java.io.PrintStream;
    021import java.io.PrintWriter;
    022import java.io.StringWriter;
    023import java.lang.reflect.InvocationTargetException;
    024import java.lang.reflect.Method;
    025import java.lang.reflect.Proxy;
    026import java.util.Iterator;
    027
    028import org.apache.commons.configuration2.event.ConfigurationErrorEvent;
    029import org.apache.commons.configuration2.event.Event;
    030import org.apache.commons.configuration2.event.EventListener;
    031import org.apache.commons.configuration2.event.EventSource;
    032import org.apache.commons.configuration2.event.EventType;
    033import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
    034import org.apache.commons.configuration2.sync.NoOpSynchronizer;
    035import org.apache.commons.configuration2.sync.Synchronizer;
    036import org.apache.commons.configuration2.tree.ExpressionEngine;
    037import org.apache.commons.logging.Log;
    038import org.apache.commons.logging.LogFactory;
    039
    040/**
    041 * Miscellaneous utility methods for configurations.
    042 *
    043 * @see ConfigurationConverter Utility methods to convert configurations.
    044 */
    045public final class ConfigurationUtils {
    046    /** Constant for the name of the clone() method. */
    047    private static final String METHOD_CLONE = "clone";
    048
    049    /**
    050     * An array with interfaces to be implemented by a proxy for an immutable configuration.
    051     */
    052    private static final Class<?>[] IMMUTABLE_CONFIG_IFCS = {ImmutableConfiguration.class};
    053
    054    /**
    055     * An array with interfaces to be implemented by a proxy for an immutable hierarchical configuration.
    056     */
    057    private static final Class<?>[] IMMUTABLE_HIERARCHICAL_CONFIG_IFCS = {ImmutableHierarchicalConfiguration.class};
    058    /**
    059     * A dummy event source that is returned by {@code asEventSource()} if a mock object has to be returned. It provides
    060     * empty dummy implementations for all interface methods.
    061     */
    062    private static final EventSource DUMMY_EVENT_SOURCE = new EventSource() {
    063
    064        @Override
    065        public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
    066        }
    067
    068        @Override
    069        public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
    070            return false;
    071        }
    072    };
    073
    074    /** The logger. */
    075    private static final Log LOG = LogFactory.getLog(ConfigurationUtils.class);
    076
    077    /**
    078     * Private constructor. Prevents instances from being created.
    079     */
    080    private ConfigurationUtils() {
    081        // to prevent instantiation...
    082    }
    083
    084    /**
    085     * Dump the configuration key/value mappings to some ouput stream.
    086     *
    087     * @param configuration the configuration
    088     * @param out the output stream to dump the configuration to
    089     * @since 2.2
    090     */
    091    public static void dump(final ImmutableConfiguration configuration, final PrintStream out) {
    092        dump(configuration, new PrintWriter(out));
    093    }
    094
    095    /**
    096     * Dump the configuration key/value mappings to some ouput stream. This version of the method exists only for backwards
    097     * compatibility reason.
    098     *
    099     * @param configuration the configuration
    100     * @param out the output stream to dump the configuration to
    101     */
    102    public static void dump(final Configuration configuration, final PrintStream out) {
    103        dump((ImmutableConfiguration) configuration, out);
    104    }
    105
    106    /**
    107     * Dump the configuration key/value mappings to some writer.
    108     *
    109     * @param configuration the configuration
    110     * @param out the writer to dump the configuration to
    111     * @since 2.2
    112     */
    113    public static void dump(final ImmutableConfiguration configuration, final PrintWriter out) {
    114        for (final Iterator<String> keys = configuration.getKeys(); keys.hasNext();) {
    115            final String key = keys.next();
    116            final Object value = configuration.getProperty(key);
    117            out.print(key);
    118            out.print("=");
    119            out.print(value);
    120
    121            if (keys.hasNext()) {
    122                out.println();
    123            }
    124        }
    125
    126        out.flush();
    127    }
    128
    129    /**
    130     * Dump the configuration key/value mappings to some writer. This version of the method exists only for backwards
    131     * compatibility reason.
    132     *
    133     * @param configuration the configuration
    134     * @param out the writer to dump the configuration to
    135     */
    136    public static void dump(final Configuration configuration, final PrintWriter out) {
    137        dump((ImmutableConfiguration) configuration, out);
    138    }
    139
    140    /**
    141     * Gets a string representation of the key/value mappings of a configuration.
    142     *
    143     * @param configuration the configuration
    144     * @return a string representation of the configuration
    145     * @since 2.2
    146     */
    147    public static String toString(final ImmutableConfiguration configuration) {
    148        final StringWriter writer = new StringWriter();
    149        dump(configuration, new PrintWriter(writer));
    150        return writer.toString();
    151    }
    152
    153    /**
    154     * Gets a string representation of the key/value mappings of a configuration. This version of the method exists only for
    155     * backwards compatibility reason.
    156     *
    157     * @param configuration the configuration
    158     * @return a string representation of the configuration
    159     */
    160    public static String toString(final Configuration configuration) {
    161        return toString((ImmutableConfiguration) configuration);
    162    }
    163
    164    /**
    165     * <p>
    166     * Copy all properties from the source configuration to the target configuration. Properties in the target configuration
    167     * are replaced with the properties with the same key in the source configuration.
    168     * </p>
    169     * <p>
    170     * <em>Note:</em> This method is not able to handle some specifics of configurations derived from
    171     * {@code AbstractConfiguration} (e.g. list delimiters). For a full support of all of these features the {@code copy()}
    172     * method of {@code AbstractConfiguration} should be used. In a future release this method might become deprecated.
    173     * </p>
    174     *
    175     * @param source the source configuration
    176     * @param target the target configuration
    177     * @since 2.2
    178     */
    179    public static void copy(final ImmutableConfiguration source, final Configuration target) {
    180        source.getKeys().forEachRemaining(key -> target.setProperty(key, source.getProperty(key)));
    181    }
    182
    183    /**
    184     * <p>
    185     * Copy all properties from the source configuration to the target configuration. Properties in the target configuration
    186     * are replaced with the properties with the same key in the source configuration.
    187     * </p>
    188     * <p>
    189     * <em>Note:</em> This method is not able to handle some specifics of configurations derived from
    190     * {@code AbstractConfiguration} (e.g. list delimiters). For a full support of all of these features the {@code copy()}
    191     * method of {@code AbstractConfiguration} should be used. In a future release this method might become deprecated.
    192     * </p>
    193     *
    194     * @param source the source configuration
    195     * @param target the target configuration
    196     * @since 1.1
    197     */
    198    public static void copy(final Configuration source, final Configuration target) {
    199        copy((ImmutableConfiguration) source, target);
    200    }
    201
    202    /**
    203     * <p>
    204     * Append all properties from the source configuration to the target configuration. Properties in the source
    205     * configuration are appended to the properties with the same key in the target configuration.
    206     * </p>
    207     * <p>
    208     * <em>Note:</em> This method is not able to handle some specifics of configurations derived from
    209     * {@code AbstractConfiguration} (e.g. list delimiters). For a full support of all of these features the {@code copy()}
    210     * method of {@code AbstractConfiguration} should be used. In a future release this method might become deprecated.
    211     * </p>
    212     *
    213     * @param source the source configuration
    214     * @param target the target configuration
    215     * @since 2.2
    216     */
    217    public static void append(final ImmutableConfiguration source, final Configuration target) {
    218        source.getKeys().forEachRemaining(key -> target.addProperty(key, source.getProperty(key)));
    219    }
    220
    221    /**
    222     * <p>
    223     * Append all properties from the source configuration to the target configuration. Properties in the source
    224     * configuration are appended to the properties with the same key in the target configuration.
    225     * </p>
    226     * <p>
    227     * <em>Note:</em> This method is not able to handle some specifics of configurations derived from
    228     * {@code AbstractConfiguration} (e.g. list delimiters). For a full support of all of these features the {@code copy()}
    229     * method of {@code AbstractConfiguration} should be used. In a future release this method might become deprecated.
    230     * </p>
    231     *
    232     * @param source the source configuration
    233     * @param target the target configuration
    234     * @since 1.1
    235     */
    236    public static void append(final Configuration source, final Configuration target) {
    237        append((ImmutableConfiguration) source, target);
    238    }
    239
    240    /**
    241     * Converts the passed in configuration to a hierarchical one. If the configuration is already hierarchical, it is
    242     * directly returned. Otherwise all properties are copied into a new hierarchical configuration.
    243     *
    244     * @param conf the configuration to convert
    245     * @return the new hierarchical configuration (the result is <b>null</b> if and only if the passed in configuration is
    246     *         <b>null</b>)
    247     * @since 1.3
    248     */
    249    public static HierarchicalConfiguration<?> convertToHierarchical(final Configuration conf) {
    250        return convertToHierarchical(conf, null);
    251    }
    252
    253    /**
    254     * Converts the passed in {@code Configuration} object to a hierarchical one using the specified
    255     * {@code ExpressionEngine}. This conversion works by adding the keys found in the configuration to a newly created
    256     * hierarchical configuration. When adding new keys to a hierarchical configuration the keys are interpreted by its
    257     * {@code ExpressionEngine}. If they contain special characters (e.g. brackets) that are treated in a special way by the
    258     * default expression engine, it may be necessary using a specific engine that can deal with such characters. Otherwise
    259     * <b>null</b> can be passed in for the {@code ExpressionEngine}; then the default expression engine is used. If the
    260     * passed in configuration is already hierarchical, it is directly returned. (However, the {@code ExpressionEngine} is
    261     * set if it is not <b>null</b>.) Otherwise all properties are copied into a new hierarchical configuration.
    262     *
    263     * @param conf the configuration to convert
    264     * @param engine the {@code ExpressionEngine} for the hierarchical configuration or <b>null</b> for the default
    265     * @return the new hierarchical configuration (the result is <b>null</b> if and only if the passed in configuration is
    266     *         <b>null</b>)
    267     * @since 1.6
    268     */
    269    public static HierarchicalConfiguration<?> convertToHierarchical(final Configuration conf, final ExpressionEngine engine) {
    270        if (conf == null) {
    271            return null;
    272        }
    273
    274        if (conf instanceof HierarchicalConfiguration) {
    275            final HierarchicalConfiguration<?> hc = (HierarchicalConfiguration<?>) conf;
    276            if (engine != null) {
    277                hc.setExpressionEngine(engine);
    278            }
    279
    280            return hc;
    281        }
    282        final BaseHierarchicalConfiguration hc = new BaseHierarchicalConfiguration();
    283        if (engine != null) {
    284            hc.setExpressionEngine(engine);
    285        }
    286
    287        // Per default, a DisabledListDelimiterHandler is set.
    288        // So list delimiters in property values are not an issue.
    289        hc.copy(conf);
    290        return hc;
    291    }
    292
    293    /**
    294     * Clones the given configuration object if this is possible. If the passed in configuration object implements the
    295     * {@code Cloneable} interface, its {@code clone()} method will be invoked. Otherwise an exception will be thrown.
    296     *
    297     * @param config the configuration object to be cloned (can be <b>null</b>)
    298     * @return the cloned configuration (<b>null</b> if the argument was <b>null</b>, too)
    299     * @throws ConfigurationRuntimeException if cloning is not supported for this object
    300     * @since 1.3
    301     */
    302    public static Configuration cloneConfiguration(final Configuration config) throws ConfigurationRuntimeException {
    303        if (config == null) {
    304            return null;
    305        }
    306        try {
    307            return (Configuration) clone(config);
    308        } catch (final CloneNotSupportedException cnex) {
    309            throw new ConfigurationRuntimeException(cnex);
    310        }
    311    }
    312
    313    /**
    314     * Returns a clone of the passed in object if cloning is supported or the object itself if not. This method checks
    315     * whether the passed in object implements the {@code Cloneable} interface. If this is the case, the {@code clone()}
    316     * method is invoked. Otherwise, the object is directly returned. Errors that might occur during reflection calls are
    317     * caught and also cause this method to return the original object.
    318     *
    319     * @param obj the object to be cloned
    320     * @return the result of the cloning attempt
    321     * @since 2.0
    322     */
    323    public static Object cloneIfPossible(final Object obj) {
    324        try {
    325            return clone(obj);
    326        } catch (final Exception ex) {
    327            return obj;
    328        }
    329    }
    330
    331    /**
    332     * An internally used helper method for cloning objects. This implementation is not very sophisticated nor efficient.
    333     * Maybe it can be replaced by an implementation from Commons Lang later. The method checks whether the passed in object
    334     * implements the {@code Cloneable} interface. If this is the case, the {@code clone()} method is invoked by reflection.
    335     * Errors that occur during the cloning process are re-thrown as runtime exceptions.
    336     *
    337     * @param obj the object to be cloned
    338     * @return the cloned object
    339     * @throws CloneNotSupportedException if the object cannot be cloned
    340     */
    341    static Object clone(final Object obj) throws CloneNotSupportedException {
    342        if (obj instanceof Cloneable) {
    343            try {
    344                final Method m = obj.getClass().getMethod(METHOD_CLONE);
    345                return m.invoke(obj);
    346            } catch (final NoSuchMethodException nmex) {
    347                throw new CloneNotSupportedException("No clone() method found for class" + obj.getClass().getName());
    348            } catch (final IllegalAccessException | InvocationTargetException itex) {
    349                throw new ConfigurationRuntimeException(itex);
    350            }
    351        }
    352        throw new CloneNotSupportedException(obj.getClass().getName() + " does not implement Cloneable");
    353    }
    354
    355    /**
    356     * Creates a clone of the specified {@code Synchronizer}. This method can be called by {@code clone()} implementations
    357     * in configuration classes that also need to copy the {@code Synchronizer} object. This method can handle some
    358     * well-known {@code Synchronizer} implementations directly. For other classes, it uses the following algorithm:
    359     * <ul>
    360     * <li>If the class of the {@code Synchronizer} has a standard constructor, a new instance is created using
    361     * reflection.</li>
    362     * <li>If this is not possible, it is tried whether the object can be cloned.</li>
    363     * </ul>
    364     * If all attempts fail, a {@code ConfigurationRuntimeException} is thrown.
    365     *
    366     * @param sync the {@code Synchronizer} object to be cloned
    367     * @return the clone of this {@code Synchronizer}
    368     * @throws ConfigurationRuntimeException if no clone can be created
    369     * @throws IllegalArgumentException if <b>null</b> is passed in
    370     */
    371    public static Synchronizer cloneSynchronizer(final Synchronizer sync) {
    372        if (sync == null) {
    373            throw new IllegalArgumentException("Synchronizer must not be null!");
    374        }
    375        if (NoOpSynchronizer.INSTANCE == sync) {
    376            return sync;
    377        }
    378
    379        try {
    380            return sync.getClass().getConstructor().newInstance();
    381        } catch (final Exception ex) {
    382            LOG.info("Cannot create new instance of " + sync.getClass());
    383        }
    384
    385        try {
    386            return (Synchronizer) clone(sync);
    387        } catch (final CloneNotSupportedException cnex) {
    388            throw new ConfigurationRuntimeException("Cannot clone Synchronizer " + sync);
    389        }
    390    }
    391
    392    /**
    393     * Enables runtime exceptions for the specified configuration object. This method can be used for configuration
    394     * implementations that may face errors on normal property access, e.g. {@code DatabaseConfiguration} or
    395     * {@code JNDIConfiguration}. Per default such errors are simply logged and then ignored. This implementation will
    396     * register a special {@link EventListener} that throws a runtime exception (namely a
    397     * {@code ConfigurationRuntimeException}) on each received error event.
    398     *
    399     * @param src the configuration, for which runtime exceptions are to be enabled; this configuration must implement
    400     *        {@link EventSource}
    401     */
    402    public static void enableRuntimeExceptions(final Configuration src) {
    403        if (!(src instanceof EventSource)) {
    404            throw new IllegalArgumentException("Configuration must implement EventSource!");
    405        }
    406        ((EventSource) src).addEventListener(ConfigurationErrorEvent.ANY, event -> {
    407            // Throw a runtime exception
    408            throw new ConfigurationRuntimeException(event.getCause());
    409        });
    410    }
    411
    412    /**
    413     * Loads the class with the given name. This method is used whenever a class has to be loaded dynamically. It first
    414     * tries the current thread's context class loader. If this fails, the class loader of this class is tried.
    415     *
    416     * @param clsName the name of the class to be loaded
    417     * @return the loaded class
    418     * @throws ClassNotFoundException if the class cannot be resolved
    419     * @since 2.0
    420     */
    421    public static Class<?> loadClass(final String clsName) throws ClassNotFoundException {
    422        if (LOG.isDebugEnabled()) {
    423            LOG.debug("Loading class " + clsName);
    424        }
    425
    426        final ClassLoader cl = Thread.currentThread().getContextClassLoader();
    427        try {
    428            if (cl != null) {
    429                return cl.loadClass(clsName);
    430            }
    431        } catch (final ClassNotFoundException cnfex) {
    432            LOG.info("Could not load class " + clsName + " using CCL. Falling back to default CL.", cnfex);
    433        }
    434
    435        return ConfigurationUtils.class.getClassLoader().loadClass(clsName);
    436    }
    437
    438    /**
    439     * Loads the class with the specified name re-throwing {@code ClassNotFoundException} exceptions as runtime exceptions.
    440     * This method works like {@link #loadClass(String)}. However, checked exceptions are caught and re-thrown as
    441     * {@code ConfigurationRuntimeException}.
    442     *
    443     * @param clsName the name of the class to be loaded
    444     * @return the loaded class
    445     * @throws ConfigurationRuntimeException if the class cannot be resolved
    446     * @since 2.0
    447     */
    448    public static Class<?> loadClassNoEx(final String clsName) {
    449        try {
    450            return loadClass(clsName);
    451        } catch (final ClassNotFoundException cnfex) {
    452            throw new ConfigurationRuntimeException("Cannot load class " + clsName, cnfex);
    453        }
    454    }
    455
    456    /**
    457     * Creates an {@code ImmutableConfiguration} from the given {@code Configuration} object. This method creates a proxy
    458     * object wrapping the original configuration and making it available under the {@code ImmutableConfiguration}
    459     * interface. Through this interface the configuration cannot be manipulated. It is also not possible to cast the
    460     * returned object back to a {@code Configuration} instance to circumvent this protection.
    461     *
    462     * @param c the {@code Configuration} to be wrapped (must not be <b>null</b>)
    463     * @return an {@code ImmutableConfiguration} view on the specified {@code Configuration} object
    464     * @throws NullPointerException if the passed in {@code Configuration} is <b>null</b>
    465     * @since 2.0
    466     */
    467    public static ImmutableConfiguration unmodifiableConfiguration(final Configuration c) {
    468        return createUnmodifiableConfiguration(IMMUTABLE_CONFIG_IFCS, c);
    469    }
    470
    471    /**
    472     * Creates an {@code ImmutableHierarchicalConfiguration} from the given {@code HierarchicalConfiguration} object. This
    473     * method works exactly like the method with the same name, but it operates on hierarchical configurations.
    474     *
    475     * @param c the {@code HierarchicalConfiguration} to be wrapped (must not be <b>null</b>)
    476     * @return an {@code ImmutableHierarchicalConfiguration} view on the specified {@code HierarchicalConfiguration} object
    477     * @throws NullPointerException if the passed in {@code HierarchicalConfiguration} is <b>null</b>
    478     * @since 2.0
    479     */
    480    public static ImmutableHierarchicalConfiguration unmodifiableConfiguration(final HierarchicalConfiguration<?> c) {
    481        return (ImmutableHierarchicalConfiguration) createUnmodifiableConfiguration(IMMUTABLE_HIERARCHICAL_CONFIG_IFCS, c);
    482    }
    483
    484    /**
    485     * Helper method for creating a proxy for an unmodifiable configuration. The interfaces the proxy should implement are
    486     * passed as argument.
    487     *
    488     * @param ifcs an array with the interface classes the proxy must implement
    489     * @param c the configuration object to be wrapped
    490     * @return a proxy object for an immutable configuration
    491     * @throws NullPointerException if the configuration is <b>null</b>
    492     */
    493    private static ImmutableConfiguration createUnmodifiableConfiguration(final Class<?>[] ifcs, final Configuration c) {
    494        return (ImmutableConfiguration) Proxy.newProxyInstance(ConfigurationUtils.class.getClassLoader(), ifcs, new ImmutableConfigurationInvocationHandler(c));
    495    }
    496
    497    /**
    498     * Casts the specified object to an {@code EventSource} if possible. The boolean argument determines the method's
    499     * behavior if the object does not implement the {@code EventSource} event: if set to <b>false</b>, a
    500     * {@code ConfigurationRuntimeException} is thrown; if set to <b>true</b>, a dummy {@code EventSource} is returned; on
    501     * this object all methods can be called, but they do not have any effect.
    502     *
    503     * @param obj the object to be cast as {@code EventSource}
    504     * @param mockIfUnsupported a flag whether a mock object should be returned if necessary
    505     * @return an {@code EventSource}
    506     * @throws ConfigurationRuntimeException if the object cannot be cast to {@code EventSource} and the mock flag is
    507     *         <b>false</b>
    508     * @since 2.0
    509     */
    510    public static EventSource asEventSource(final Object obj, final boolean mockIfUnsupported) {
    511        if (obj instanceof EventSource) {
    512            return (EventSource) obj;
    513        }
    514
    515        if (!mockIfUnsupported) {
    516            throw new ConfigurationRuntimeException("Cannot cast to EventSource: " + obj);
    517        }
    518        return DUMMY_EVENT_SOURCE;
    519    }
    520}