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}