    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 */
    017package org.apache.commons.configuration2.builder;
    018
    019import java.util.Collection;
    020import java.util.LinkedList;
    021import java.util.concurrent.CopyOnWriteArrayList;
    022
    023/**
    024 * <p>
    025 * A class for managing a set of {@link DefaultParametersHandler} objects.
    026 * </p>
    027 * <p>
    028 * This class provides functionality for registering and removing
    029 * {@code DefaultParametersHandler} objects for arbitrary parameters classes.
    030 * The handlers registered at an instance can then be applied on a passed in
    031 * parameters object, so that it gets initialized with the provided default
    032 * values.
    033 * </p>
    034 * <p>
    035 * Usage of this class is as follows: First the {@code DefaultParametersHandler}
    036 * objects to be supported must be registered using one of the
    037 * {@code registerDefaultHandler()} methods. After that arbitrary parameters
    038 * objects can be passed to the {@code initializeParameters()} method. This
    039 * causes all {@code DefaultParametersHandler} objects supporting this
    040 * parameters class to be invoked on this object.
    041 * </p>
    042 * <p>
    043 * Implementation note: This class is thread-safe.
    044 * </p>
    045 *
    046 * @since 2.0
    047 */
    048public class DefaultParametersManager
    049{
    050    /** A collection with the registered default handlers. */
    051    private final Collection<DefaultHandlerData> defaultHandlers;
    052
    053    /**
    054     * Creates a new instance of {@code DefaultParametersManager}.
    055     */
    056    public DefaultParametersManager()
    057    {
    058        defaultHandlers = new CopyOnWriteArrayList<>();
    059    }
    060
    061    /**
    062     * Registers the specified {@code DefaultParametersHandler} object for the
    063     * given parameters class. This means that this handler object is invoked
    064     * every time a parameters object of the specified class or one of its
    065     * subclasses is initialized. The handler can set arbitrary default values
    066     * for the properties supported by this parameters object. If there are
    067     * multiple handlers registered supporting a specific parameters class, they
    068     * are invoked in the order in which they were registered. So handlers
    069     * registered later may override the values set by handlers registered
    070     * earlier.
    071     *
    072     * @param <T> the type of the parameters supported by this handler
    073     * @param paramsClass the parameters class supported by this handler (must
    074     *        not be <b>null</b>)
    075     * @param handler the {@code DefaultParametersHandler} to be registered
    076     *        (must not be <b>null</b>)
    077     * @throws IllegalArgumentException if a required parameter is missing
    078     */
    079    public <T> void registerDefaultsHandler(final Class<T> paramsClass,
    080            final DefaultParametersHandler<? super T> handler)
    081    {
    082        registerDefaultsHandler(paramsClass, handler, null);
    083    }
    084
    085    /**
    086     * Registers the specified {@code DefaultParametersHandler} object for the
    087     * given parameters class and start class in the inheritance hierarchy. This
    088     * method works like
    089     * {@link #registerDefaultsHandler(Class, DefaultParametersHandler)}, but
    090     * the defaults handler is only executed on parameter objects that are
    091     * instances of the specified start class. Parameter classes do not stand in
    092     * a real inheritance hierarchy; however, there is a logic hierarchy defined
    093     * by the methods supported by the different parameter objects. A properties
    094     * parameter object for instance supports all methods defined for a
    095     * file-based parameter object. So one can argue that
    096     * {@link org.apache.commons.configuration2.builder.fluent.FileBasedBuilderParameters
    097     * FileBasedBuilderParameters} is a base interface of
    098     * {@link org.apache.commons.configuration2.builder.fluent.PropertiesBuilderParameters
    099     * PropertiesBuilderParameters} (although, for technical reasons,
    100     * this relation is not reflected in the Java classes). A
    101     * {@link DefaultParametersHandler} object defined for a base interface can
    102     * also deal with parameter objects "derived" from this base interface (i.e.
    103     * supporting a super set of the methods defined by the base interface). Now
    104     * there may be the use case that there is an implementation of
    105     * {@code DefaultParametersHandler} for a base interface (e.g.
    106     * {@code FileBasedBuilderParameters}), but it should only process specific
    107     * derived interfaces (say {@code PropertiesBuilderParameters}, but not
    108     * {@link org.apache.commons.configuration2.builder.fluent.XMLBuilderParameters
    109     * XMLBuilderParameters}). This can be achieved by passing in
    110     * {@code PropertiesBuilderParameters} as start class. In this case,
    111     * {@code DefaultParametersManager} ensures that the handler is only called
    112     * on parameter objects having both the start class and the actual type
    113     * supported by the handler as base interfaces. The passed in start class
    114     * can be <b>null</b>; then the parameter class supported by the handler is
    115     * used (which is the default behavior of the
    116     * {@link #registerDefaultsHandler(Class, DefaultParametersHandler)}
    117     * method).
    118     *
    119     * @param <T> the type of the parameters supported by this handler
    120     * @param paramsClass the parameters class supported by this handler (must
    121     *        not be <b>null</b>)
    122     * @param handler the {@code DefaultParametersHandler} to be registered
    123     *        (must not be <b>null</b>)
    124     * @param startClass an optional start class in the hierarchy of parameter
    125     *        objects for which this handler should be applied
    126     * @throws IllegalArgumentException if a required parameter is missing
    127     */
    128    public <T> void registerDefaultsHandler(final Class<T> paramsClass,
    129            final DefaultParametersHandler<? super T> handler, final Class<?> startClass)
    130    {
    131        if (paramsClass == null)
    132        {
    133            throw new IllegalArgumentException(
    134                    "Parameters class must not be null!");
    135        }
    136        if (handler == null)
    137        {
    138            throw new IllegalArgumentException(
    139                    "DefaultParametersHandler must not be null!");
    140        }
    141        defaultHandlers.add(new DefaultHandlerData(handler, paramsClass,
    142                startClass));
    143    }
    144
    145    /**
    146     * Removes the specified {@code DefaultParametersHandler} from this
    147     * instance. If this handler has been registered multiple times for
    148     * different start classes, all occurrences are removed.
    149     *
    150     * @param handler the {@code DefaultParametersHandler} to be removed
    151     */
    152    public void unregisterDefaultsHandler(final DefaultParametersHandler<?> handler)
    153    {
    154        unregisterDefaultsHandler(handler, null);
    155    }
    156
    157    /**
    158     * Removes the specified {@code DefaultParametersHandler} from this instance
    159     * if it is in combination with the given start class. If this handler has
    160     * been registered multiple times for different start classes, only
    161     * occurrences for the given start class are removed. The {@code startClass}
    162     * parameter can be <b>null</b>, then all occurrences of the handler are
    163     * removed.
    164     *
    165     * @param handler the {@code DefaultParametersHandler} to be removed
    166     * @param startClass the start class for which this handler is to be removed
    167     */
    168    public void unregisterDefaultsHandler(final DefaultParametersHandler<?> handler,
    169            final Class<?> startClass)
    170    {
    171        final Collection<DefaultHandlerData> toRemove =
    172                new LinkedList<>();
    173        for (final DefaultHandlerData dhd : defaultHandlers)
    174        {
    175            if (dhd.isOccurrence(handler, startClass))
    176            {
    177                toRemove.add(dhd);
    178            }
    179        }
    180
    181        defaultHandlers.removeAll(toRemove);
    182    }
    183
    184    /**
    185     * Initializes the passed in {@code BuilderParameters} object by applying
    186     * all matching {@link DefaultParametersHandler} objects registered at this
    187     * instance. Using this method the passed in parameters object can be
    188     * populated with default values.
    189     *
    190     * @param params the parameters object to be initialized (may be
    191     *        <b>null</b>, then this method has no effect)
    192     */
    193    public void initializeParameters(final BuilderParameters params)
    194    {
    195        if (params != null)
    196        {
    197            for (final DefaultHandlerData dhd : defaultHandlers)
    198            {
    199                dhd.applyHandlerIfMatching(params);
    200            }
    201        }
    202    }
    203
    204    /**
    205     * A data class storing information about {@code DefaultParametersHandler}
    206     * objects added to a {@code Parameters} object. Using this class it is
    207     * possible to find out which default handlers apply for a given parameters
    208     * object and to invoke them.
    209     */
    210    private static class DefaultHandlerData
    211    {
    212        /** The handler object. */
    213        private final DefaultParametersHandler<?> handler;
    214
    215        /** The class supported by this handler. */
    216        private final Class<?> parameterClass;
    217
    218        /** The start class for applying this handler. */
    219        private final Class<?> startClass;
    220
    221        /**
    222         * Creates a new instance of {@code DefaultHandlerData}.
    223         *
    224         * @param h the {@code DefaultParametersHandler}
    225         * @param cls the handler's data class
    226         * @param startCls the start class
    227         */
    228        public DefaultHandlerData(final DefaultParametersHandler<?> h, final Class<?> cls,
    229                final Class<?> startCls)
    230        {
    231            handler = h;
    232            parameterClass = cls;
    233            startClass = startCls;
    234        }
    235
    236        /**
    237         * Checks whether the managed {@code DefaultParametersHandler} can be
    238         * applied to the given parameters object. If this is the case, it is
    239         * executed on this object and can initialize it with default values.
    240         *
    241         * @param obj the parameters object to be initialized
    242         */
    243        @SuppressWarnings("unchecked")
    244        // There are explicit isInstance() checks, so there won't be
    245        // ClassCastExceptions
    246        public void applyHandlerIfMatching(final BuilderParameters obj)
    247        {
    248            if (parameterClass.isInstance(obj)
    249                    && (startClass == null || startClass.isInstance(obj)))
    250            {
    251                @SuppressWarnings("rawtypes")
    252                final
    253                DefaultParametersHandler handlerUntyped = handler;
    254                handlerUntyped.initializeDefaults(obj);
    255            }
    256        }
    257
    258        /**
    259         * Tests whether this instance refers to the specified occurrence of a
    260         * {@code DefaultParametersHandler}.
    261         *
    262         * @param h the handler to be checked
    263         * @param startCls the start class
    264         * @return <b>true</b> if this instance refers to this occurrence,
    265         *         <b>false</b> otherwise
    266         */
    267        public boolean isOccurrence(final DefaultParametersHandler<?> h,
    268                final Class<?> startCls)
    269        {
    270            return h == handler
    271                    && (startCls == null || startCls.equals(startClass));
    272        }
    273    }
    274}