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