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
015package org.apache.hivemind.lib.util;
016
017import java.util.HashMap;
018import java.util.Iterator;
019import java.util.LinkedList;
020import java.util.Map;
021import java.util.WeakHashMap;
022
023import org.apache.hivemind.service.ClassFabUtils;
024import 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
033public 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}