    001    // Copyright 2006, 2007, 2008 The Apache Software Foundation
    002    //
    003    // Licensed under the Apache License, Version 2.0 (the "License");
    004    // you may not use this file except in compliance with the License.
    005    // You may obtain a copy of the License at
    006    //
    007    //     http://www.apache.org/licenses/LICENSE-2.0
    008    //
    009    // Unless required by applicable law or agreed to in writing, software
    010    // distributed under the License is distributed on an "AS IS" BASIS,
    011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012    // See the License for the specific language governing permissions and
    013    // limitations under the License.
    014
    015    package org.apache.tapestry5.ioc.util;
    016
    017    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    018    import org.apache.tapestry5.ioc.internal.util.InheritanceSearch;
    019
    020    import java.util.Collection;
    021    import java.util.List;
    022    import java.util.Map;
    023
    024    /**
    025     * A key component in implementing the "Gang of Four" Strategy pattern. A StrategyRegistry will match up a given input
    026     * type with a registered strategy for that type.
    027     *
    028     * @param <A> the type of the strategy adapter
    029     */
    030    public final class StrategyRegistry<A>
    031    {
    032        private final Class<A> adapterType;
    033
    034        private final boolean allowNonMatch;
    035
    036        private final Map<Class, A> registrations = CollectionFactory.newMap();
    037
    038        private final Map<Class, A> cache = CollectionFactory.newConcurrentMap();
    039
    040        /**
    041         * Used to identify types for which there is no matching adapter; we're using it as if it were a ConcurrentSet.
    042         */
    043        private final Map<Class, Boolean> unmatched = CollectionFactory.newConcurrentMap();
    044
    045        private StrategyRegistry(Class<A> adapterType, Map<Class, A> registrations, boolean allowNonMatch)
    046        {
    047            this.adapterType = adapterType;
    048            this.allowNonMatch = allowNonMatch;
    049
    050            this.registrations.putAll(registrations);
    051        }
    052
    053        /**
    054         * Creates a strategy registry for the given adapter type. The registry will be configured to require matches.
    055         *
    056         * @param adapterType   the type of adapter retrieved from the registry
    057         * @param registrations map of registrations (the contents of the map are copied)
    058         */
    059        public static <A> StrategyRegistry<A> newInstance(Class<A> adapterType,
    060                                                          Map<Class, A> registrations)
    061        {
    062            return newInstance(adapterType, registrations, false);
    063        }
    064
    065        /**
    066         * Creates a strategy registry for the given adapter type.
    067         *
    068         * @param adapterType   the type of adapter retrieved from the registry
    069         * @param registrations map of registrations (the contents of the map are copied)
    070         * @param allowNonMatch if true, then the registry supports non-matches when retrieving an adapter
    071         */
    072        public static <A> StrategyRegistry<A> newInstance(
    073                Class<A> adapterType,
    074                Map<Class, A> registrations, boolean allowNonMatch)
    075        {
    076            return new StrategyRegistry<A>(adapterType, registrations, allowNonMatch);
    077        }
    078
    079        public void clearCache()
    080        {
    081            cache.clear();
    082            unmatched.clear();
    083        }
    084
    085        public Class<A> getAdapterType()
    086        {
    087            return adapterType;
    088        }
    089
    090        /**
    091         * Gets an adapter for an object. Searches based on the value's class, unless the value is null, in which case, a
    092         * search on class void is used.
    093         *
    094         * @param value for which an adapter is needed
    095         * @return the adapter for the value or null if not found (and allowNonMatch is true)
    096         * @throws IllegalArgumentException if no matching adapter may be found and allowNonMatch is false
    097         */
    098
    099        public A getByInstance(Object value)
    100        {
    101            return get(value == null ? void.class : value.getClass());
    102        }
    103
    104        /**
    105         * Searches for an adapter corresponding to the given input type.
    106         *
    107         * @param type the type to search
    108         * @return the adapter for the type or null if not found (and allowNonMatch is true)
    109         * @throws IllegalArgumentException if no matching adapter may be found   and allowNonMatch is false
    110         */
    111        public A get(Class type)
    112        {
    113
    114            A result = cache.get(type);
    115
    116            if (result != null) return result;
    117
    118            if (unmatched.containsKey(type)) return null;
    119
    120
    121            result = findMatch(type);
    122
    123            // This may be null in the case that there is no match and we're allowing that to not
    124            // be an error.  That's why we check via containsKey.
    125
    126            if (result != null)
    127            {
    128                cache.put(type, result);
    129            }
    130            else
    131            {
    132                unmatched.put(type, true);
    133            }
    134
    135            return result;
    136        }
    137
    138        private A findMatch(Class type)
    139        {
    140            for (Class t : new InheritanceSearch(type))
    141            {
    142                A result = registrations.get(t);
    143
    144                if (result != null) return result;
    145            }
    146
    147            if (allowNonMatch) return null;
    148
    149            // Report the error. These things really confused the hell out of people in Tap4, so we're
    150            // going the extra mile on the exception message.
    151
    152            List<String> names = CollectionFactory.newList();
    153            for (Class t : registrations.keySet())
    154                names.add(t.getName());
    155
    156            throw new IllegalArgumentException(UtilMessages
    157                    .noStrategyAdapter(type, adapterType, names));
    158        }
    159
    160        /**
    161         * Returns the registered types for which adapters are available.
    162         */
    163        public Collection<Class> getTypes()
    164        {
    165            return CollectionFactory.newList(registrations.keySet());
    166        }
    167
    168        @Override
    169        public String toString()
    170        {
    171            return String.format("StrategyRegistry[%s]", adapterType.getName());
    172        }
    173    }