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    }