/*
 * 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.configuration2;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Iterator;

import org.apache.commons.configuration2.event.ConfigurationErrorEvent;
import org.apache.commons.configuration2.event.Event;
import org.apache.commons.configuration2.event.EventListener;
import org.apache.commons.configuration2.event.EventSource;
import org.apache.commons.configuration2.event.EventType;
import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
import org.apache.commons.configuration2.sync.NoOpSynchronizer;
import org.apache.commons.configuration2.sync.Synchronizer;
import org.apache.commons.configuration2.tree.ExpressionEngine;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 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 1624601 2014-09-12 18:04:36Z oheger $
 */
public final class ConfigurationUtils
{
    /** Constant for the name of the clone() method.*/
    private static final String METHOD_CLONE = "clone";

    /**
     * An array with interfaces to be implemented by a proxy for an immutable
     * configuration.
     */
    private static final Class<?>[] IMMUTABLE_CONFIG_IFCS = {
        ImmutableConfiguration.class
    };

    /**
     * An array with interfaces to be implemented by a proxy for an immutable
     * hierarchical configuration.
     */
    private static final Class<?>[] IMMUTABLE_HIERARCHICAL_CONFIG_IFCS = {
        ImmutableHierarchicalConfiguration.class
    };
    /**
     * A dummy event source that is returned by {@code asEventSource()} if a
     * mock object has to be returned. It provides empty dummy implementations
     * for all interface methods.
     */
    private static final EventSource DUMMY_EVENT_SOURCE = new EventSource()
    {

        @Override
        public <T extends Event> void addEventListener(EventType<T> eventType,
                EventListener<? super T> listener)
        {
        }

        @Override
        public <T extends Event> boolean removeEventListener(
                EventType<T> eventType, EventListener<? super T> listener)
        {
            return false;
        }
    };

    /** The logger.*/
    private static final Log LOG = LogFactory.getLog(ConfigurationUtils.class);

    /**
     * Private constructor. Prevents instances from being created.
     */
    private ConfigurationUtils()
    {
        // to prevent instantiation...
    }

    /**
     * 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.
     *
     * @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.
     *
     * @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();
    }

    /**
     * <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));
        }
    }

    /**
     * <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.
     *
     * @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 {@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 = (HierarchicalConfiguration<?>) conf;
            if (engine != null)
            {
                hc.setExpressionEngine(engine);
            }

            return hc;
        }
        else
        {
            BaseHierarchicalConfiguration hc = new BaseHierarchicalConfiguration();
            if (engine != null)
            {
                hc.setExpressionEngine(engine);
            }

            // Per default, a DisabledListDelimiterHandler is set.
            // So list delimiters in property values are not an issue.
            hc.append(conf);
            return hc;
        }
    }

    /**
     * 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);
            }
        }
    }

    /**
     * Returns a clone of the passed in object if cloning is supported or the
     * object itself if not. This method checks whether the passed in object
     * implements the {@code Cloneable} interface. If this is the case, the
     * {@code clone()} method is invoked. Otherwise, the object is directly
     * returned. Errors that might occur during reflection calls are caught and
     * also cause this method to return the original object.
     *
     * @param obj the object to be cloned
     * @return the result of the cloning attempt
     * @since 2.0
     */
    public static Object cloneIfPossible(Object obj)
    {
        try
        {
            return clone(obj);
        }
        catch (Exception ex)
        {
            return obj;
        }
    }

    /**
     * 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");
        }
    }

    /**
     * Creates a clone of the specified {@code Synchronizer}. This method can be
     * called by {@code clone()} implementations in configuration classes that
     * also need to copy the {@code Synchronizer} object. This method can handle
     * some well-known {@code Synchronizer} implementations directly. For other
     * classes, it uses the following algorithm:
     * <ul>
     * <li>If the class of the {@code Synchronizer} has a standard constructor,
     * a new instance is created using reflection.</li>
     * <li>If this is not possible, it is tried whether the object can be
     * cloned.</li>
     * </ul>
     * If all attempts fail, a {@code ConfigurationRuntimeException} is thrown.
     *
     * @param sync the {@code Synchronizer} object to be cloned
     * @return the clone of this {@code Synchronizer}
     * @throws ConfigurationRuntimeException if no clone can be created
     * @throws IllegalArgumentException if <b>null</b> is passed in
     */
    public static Synchronizer cloneSynchronizer(Synchronizer sync)
    {
        if (sync == null)
        {
            throw new IllegalArgumentException("Synchronizer must not be null!");
        }
        if (NoOpSynchronizer.INSTANCE == sync)
        {
            return sync;
        }

        try
        {
            return sync.getClass().newInstance();
        }
        catch (Exception ex)
        {
            LOG.info("Cannot create new instance of " + sync.getClass());
        }

        try
        {
            return (Synchronizer) clone(sync);
        }
        catch (CloneNotSupportedException cnex)
        {
            throw new ConfigurationRuntimeException(
                    "Cannot clone Synchronizer " + sync);
        }
    }

    /**
     * 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 EventListener} 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 implement {@link EventSource}
     */
    public static void enableRuntimeExceptions(Configuration src)
    {
        if (!(src instanceof EventSource))
        {
            throw new IllegalArgumentException(
                    "Configuration must implement EventSource!");
        }
        ((EventSource) src).addEventListener(ConfigurationErrorEvent.ANY,
                new EventListener<ConfigurationErrorEvent>()
                {
                    @Override
                    public void onEvent(ConfigurationErrorEvent event)
                    {
                        // Throw a runtime exception
                        throw new ConfigurationRuntimeException(event
                                .getCause());
                    }
                });
    }

    /**
     * Loads the class with the given name. This method is used whenever a class
     * has to be loaded dynamically. It first tries the current thread's context
     * class loader. If this fails, the class loader of this class is tried.
     *
     * @param clsName the name of the class to be loaded
     * @return the loaded class
     * @throws ClassNotFoundException if the class cannot be resolved
     * @since 2.0
     */
    public static Class<?> loadClass(String clsName)
            throws ClassNotFoundException
    {
        if (LOG.isDebugEnabled())
        {
            LOG.debug("Loading class " + clsName);
        }

        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        try
        {
            if (cl != null)
            {
                return cl.loadClass(clsName);
            }
        }
        catch (ClassNotFoundException cnfex)
        {
            LOG.info("Could not load class " + clsName
                    + " using CCL. Falling back to default CL.", cnfex);
        }

        return ConfigurationUtils.class.getClassLoader().loadClass(clsName);
    }

    /**
     * Loads the class with the specified name re-throwing
     * {@code ClassNotFoundException} exceptions as runtime exceptions. This
     * method works like {@link #loadClass(String)}. However, checked exceptions
     * are caught and re-thrown as {@code ConfigurationRuntimeException}.
     *
     * @param clsName the name of the class to be loaded
     * @return the loaded class
     * @throws ConfigurationRuntimeException if the class cannot be resolved
     * @since 2.0
     */
    public static Class<?> loadClassNoEx(String clsName)
    {
        try
        {
            return loadClass(clsName);
        }
        catch (ClassNotFoundException cnfex)
        {
            throw new ConfigurationRuntimeException("Cannot load class "
                    + clsName, cnfex);
        }
    }

    /**
     * Creates an {@code ImmutableConfiguration} from the given
     * {@code Configuration} object. This method creates a proxy object wrapping
     * the original configuration and making it available under the
     * {@code ImmutableConfiguration} interface. Through this interface the
     * configuration cannot be manipulated. It is also not possible to cast the
     * returned object back to a {@code Configuration} instance to circumvent
     * this protection.
     *
     * @param c the {@code Configuration} to be wrapped (must not be
     *        <b>null</b>)
     * @return an {@code ImmutableConfiguration} view on the specified
     *         {@code Configuration} object
     * @throws NullPointerException if the passed in {@code Configuration} is
     *         <b>null</b>
     * @since 2.0
     */
    public static ImmutableConfiguration unmodifiableConfiguration(
            Configuration c)
    {
        return createUnmodifiableConfiguration(IMMUTABLE_CONFIG_IFCS, c);
    }

    /**
     * Creates an {@code ImmutableHierarchicalConfiguration} from the given
     * {@code HierarchicalConfiguration} object. This method works exactly like
     * the method with the same name, but it operates on hierarchical
     * configurations.
     *
     * @param c the {@code HierarchicalConfiguration} to be wrapped (must not be
     *        <b>null</b>)
     * @return an {@code ImmutableHierarchicalConfiguration} view on the
     *         specified {@code HierarchicalConfiguration} object
     * @throws NullPointerException if the passed in
     *         {@code HierarchicalConfiguration} is <b>null</b>
     * @since 2.0
     */
    public static ImmutableHierarchicalConfiguration unmodifiableConfiguration(
            HierarchicalConfiguration<?> c)
    {
        return (ImmutableHierarchicalConfiguration) createUnmodifiableConfiguration(
                IMMUTABLE_HIERARCHICAL_CONFIG_IFCS, c);
    }

    /**
     * Helper method for creating a proxy for an unmodifiable configuration. The
     * interfaces the proxy should implement are passed as argument.
     *
     * @param ifcs an array with the interface classes the proxy must implement
     * @param c the configuration object to be wrapped
     * @return a proxy object for an immutable configuration
     * @throws NullPointerException if the configuration is <b>null</b>
     */
    private static ImmutableConfiguration createUnmodifiableConfiguration(
            Class<?>[] ifcs, Configuration c)
    {
        return (ImmutableConfiguration) Proxy.newProxyInstance(
                ConfigurationUtils.class.getClassLoader(), ifcs,
                new ImmutableConfigurationInvocationHandler(c));
    }

    /**
     * Casts the specified object to an {@code EventSource} if possible. The
     * boolean argument determines the method's behavior if the object does not
     * implement the {@code EventSource} event: if set to <b>false</b>, a
     * {@code ConfigurationRuntimeException} is thrown; if set to <b>true</b>, a
     * dummy {@code EventSource} is returned; on this object all methods can be
     * called, but they do not have any effect.
     *
     * @param obj the object to be cast as {@code EventSource}
     * @param mockIfUnsupported a flag whether a mock object should be returned
     *        if necessary
     * @return an {@code EventSource}
     * @throws ConfigurationRuntimeException if the object cannot be cast to
     *         {@code EventSource} and the mock flag is <b>false</b>
     * @since 2.0
     */
    public static EventSource asEventSource(Object obj,
            boolean mockIfUnsupported)
    {
        if (obj instanceof EventSource)
        {
            return (EventSource) obj;
        }

        if (!mockIfUnsupported)
        {
            throw new ConfigurationRuntimeException(
                    "Cannot cast to EventSource: " + obj);
        }
        return DUMMY_EVENT_SOURCE;
    }
}
