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}