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 }