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 }