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 1790899 2017-04-10 21:56:46Z 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(Class<T> paramsClass,
081            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(Class<T> paramsClass,
130            DefaultParametersHandler<? super T> handler, 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(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(DefaultParametersHandler<?> handler,
170            Class<?> startClass)
171    {
172        Collection<DefaultHandlerData> toRemove =
173                new LinkedList<>();
174        for (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(BuilderParameters params)
195    {
196        if (params != null)
197        {
198            for (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(DefaultParametersHandler<?> h, Class<?> cls,
230                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(BuilderParameters obj)
248        {
249            if (parameterClass.isInstance(obj)
250                    && (startClass == null || startClass.isInstance(obj)))
251            {
252                @SuppressWarnings("rawtypes")
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(DefaultParametersHandler<?> h,
268                Class<?> startCls)
269        {
270            return h == handler
271                    && (startCls == null || startCls.equals(startClass));
272        }
273    }
274}