001    // Copyright 2004, 2005 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.hivemind.lib.util;
016    
017    import java.util.HashMap;
018    import java.util.Iterator;
019    import java.util.LinkedList;
020    import java.util.Map;
021    import java.util.WeakHashMap;
022    
023    import org.apache.hivemind.service.ClassFabUtils;
024    import org.apache.hivemind.util.Defense;
025    
026    /**
027     * Thread-safe implementation of {@link org.apache.hivemind.lib.util.StrategyRegistry}.
028     * 
029     * @author Howard Lewis Ship
030     * @since 1.1
031     */
032    
033    public class StrategyRegistryImpl implements StrategyRegistry
034    {
035        /**
036         * A Map of adaptor objects, keyed on registration Class.
037         */
038    
039        private Map _registrations = new HashMap();
040    
041        /**
042         * A Map of adaptor objects, keyed on subject Class.
043         */
044    
045        private Map _cache = new WeakHashMap();
046    
047        public synchronized void register(Class registrationClass, Object adaptor)
048        {
049            Defense.notNull(registrationClass, "registrationClass");
050            Defense.notNull(adaptor, "adaptor");
051    
052            if (_registrations.containsKey(registrationClass))
053                throw new IllegalArgumentException(UtilMessages
054                        .duplicateRegistration(registrationClass));
055    
056            _registrations.put(registrationClass, adaptor);
057    
058            // Can't tell what is and isn't valid in the cache.
059            // Also, normally all registrations occur before any adaptors
060            // are searched for, so this is not a big deal.
061    
062            _cache.clear();
063        }
064    
065        public synchronized Object getStrategy(Class subjectClass)
066        {
067            Defense.notNull(subjectClass, "subjectClass");
068    
069            Object result = _cache.get(subjectClass);
070    
071            if (result != null)
072                return result;
073    
074            result = searchForAdaptor(subjectClass);
075    
076            // Record the result in the cache
077    
078            _cache.put(subjectClass, result);
079    
080            return result;
081        }
082    
083        /**
084         * Searches the registration Map for a match, based on inheritance.
085         * <p>
086         * Searches class inheritance first, then interfaces (in a rather vague order). Really should
087         * match the order from the JVM spec.
088         * <p>
089         * There's a degenerate case where we may check the same interface more than once:
090         * <ul>
091         * <li>Two interfaces, I1 and I2
092         * <li>Two classes, C1 and C2
093         * <li>I2 extends I1
094         * <li>C2 extends C1
095         * <li>C1 implements I1
096         * <li>C2 implements I2
097         * <li>The search will be: C2, C1, I2, I1, I1
098         * <li>I1 is searched twice, because C1 implements it, and I2 extends it
099         * <li>There are other such cases, but none of them cause infinite loops and most are rare (we
100         * could guard against it, but its relatively expensive).
101         * <li>Multiple checks only occur if we don't find a registration
102         * </ul>
103         * <p>
104         * This method is only called from a synchronized block, so it is implicitly synchronized.
105         */
106    
107        private Object searchForAdaptor(Class subjectClass)
108        {
109            LinkedList queue = null;
110            Object result = null;
111    
112            // Step one: work up through the class inheritance.
113    
114            Class searchClass = subjectClass;
115    
116            // Primitive types have null, not Object, as their parent
117            // class.
118    
119            while (searchClass != Object.class && searchClass != null)
120            {
121                result = _registrations.get(searchClass);
122                if (result != null)
123                    return result;
124    
125                // Not an exact match. If the search class
126                // implements any interfaces, add them to the queue.
127    
128                Class[] interfaces = searchClass.getInterfaces();
129                int length = interfaces.length;
130    
131                if (queue == null && length > 0)
132                    queue = new LinkedList();
133    
134                for (int i = 0; i < length; i++)
135                    queue.addLast(interfaces[i]);
136    
137                // Advance up to the next superclass
138    
139                searchClass = getSuperclass(searchClass);
140    
141            }
142    
143            // Ok, the easy part failed, lets start searching
144            // interfaces.
145    
146            if (queue != null)
147            {
148                while (!queue.isEmpty())
149                {
150                    searchClass = (Class) queue.removeFirst();
151    
152                    result = _registrations.get(searchClass);
153                    if (result != null)
154                        return result;
155    
156                    // Interfaces can extend other interfaces; add them
157                    // to the queue.
158    
159                    Class[] interfaces = searchClass.getInterfaces();
160                    int length = interfaces.length;
161    
162                    for (int i = 0; i < length; i++)
163                        queue.addLast(interfaces[i]);
164                }
165            }
166    
167            // Not a match on interface; our last gasp is to check
168            // for a registration for java.lang.Object
169    
170            result = _registrations.get(Object.class);
171            if (result != null)
172                return result;
173    
174            // No match? That's rare ... and an error.
175    
176            throw new IllegalArgumentException(UtilMessages.strategyNotFound(subjectClass));
177        }
178    
179        /**
180         * Returns the superclass of the given class, with a single tweak: If the search class is an
181         * array class, and the component type is an object class (but not Object), then the simple
182         * Object array class is returned. This reflects the fact that an array of any class may be
183         * assignable to <code>Object[]</code>, even though the superclass of an array is always
184         * simply <code>Object</code>.
185         */
186    
187        private Class getSuperclass(Class searchClass)
188        {
189            if (searchClass.isArray())
190            {
191                Class componentType = searchClass.getComponentType();
192    
193                if (!componentType.isPrimitive() && componentType != Object.class)
194                    return Object[].class;
195            }
196    
197            return searchClass.getSuperclass();
198        }
199    
200        public synchronized String toString()
201        {
202            StringBuffer buffer = new StringBuffer();
203            buffer.append("AdaptorRegistry[");
204    
205            Iterator i = _registrations.entrySet().iterator();
206            boolean showSep = false;
207    
208            while (i.hasNext())
209            {
210                if (showSep)
211                    buffer.append(' ');
212    
213                Map.Entry entry = (Map.Entry) i.next();
214    
215                Class registeredClass = (Class) entry.getKey();
216    
217                buffer.append(ClassFabUtils.getJavaClassName(registeredClass));
218                buffer.append("=");
219                buffer.append(entry.getValue());
220    
221                showSep = true;
222            }
223    
224            buffer.append("]");
225    
226            return buffer.toString();
227        }
228    }