1 16 17 package org.cojen.util; 18 19 import java.lang.ref.SoftReference ; 20 import java.lang.reflect.Method ; 21 import java.util.ArrayList ; 22 import java.util.Iterator ; 23 import java.util.List ; 24 import java.util.Map ; 25 import java.math.BigInteger ; 26 import org.cojen.classfile.ClassFile; 27 import org.cojen.classfile.CodeBuilder; 28 import org.cojen.classfile.Label; 29 import org.cojen.classfile.LocalVariable; 30 import org.cojen.classfile.MethodInfo; 31 import org.cojen.classfile.Modifiers; 32 import org.cojen.classfile.Opcode; 33 import org.cojen.classfile.TypeDesc; 34 35 43 public abstract class BeanPropertyAccessor { 44 private static Map cAccessors = new WeakIdentityMap(); 46 47 50 public static BeanPropertyAccessor forClass(Class clazz) { 51 synchronized (cAccessors) { 52 BeanPropertyAccessor bpa; 53 SoftReference ref = (SoftReference ) cAccessors.get(clazz); 54 if (ref != null) { 55 bpa = (BeanPropertyAccessor)ref.get(); 56 if (bpa != null) { 57 return bpa; 58 } 59 } 60 bpa = generate(clazz); 61 cAccessors.put(clazz, new SoftReference (bpa)); 62 return bpa; 63 } 64 } 65 66 private static BeanPropertyAccessor generate(Class beanType) { 67 ClassInjector ci = ClassInjector.create 68 (BeanPropertyAccessor.class.getName(), beanType.getClassLoader()); 69 Class clazz = ci.defineClass(generateClassFile(ci.getClassName(), beanType)); 70 71 try { 72 return (BeanPropertyAccessor)clazz.newInstance(); 73 } catch (InstantiationException e) { 74 throw new InternalError (e.toString()); 75 } catch (IllegalAccessException e) { 76 throw new InternalError (e.toString()); 77 } 78 } 79 80 private static ClassFile generateClassFile(String className, 81 Class beanType) 82 { 83 BeanProperty[][] props = getBeanProperties(beanType); 84 85 ClassFile cf = new ClassFile(className, BeanPropertyAccessor.class); 86 cf.markSynthetic(); 87 cf.setSourceFile(BeanPropertyAccessor.class.getName()); 88 try { 89 cf.setTarget(System.getProperty("java.specification.version")); 90 } catch (Exception e) { 91 } 92 93 MethodInfo ctor = cf.addConstructor(Modifiers.PUBLIC, null); 94 ctor.markSynthetic(); 95 CodeBuilder builder = new CodeBuilder(ctor); 96 97 builder.loadThis(); 98 builder.invokeSuperConstructor(null); 99 builder.returnVoid(); 100 101 generateMethod(cf, beanType, props[0], true); 102 generateMethod(cf, beanType, props[1], false); 103 104 return cf; 105 } 106 107 private static void generateMethod(ClassFile cf, 108 Class beanType, 109 BeanProperty[] properties, 110 boolean forRead) 111 { 112 TypeDesc objectType = TypeDesc.OBJECT; 113 TypeDesc stringType = TypeDesc.STRING; 114 TypeDesc intType = TypeDesc.INT; 115 TypeDesc booleanType = TypeDesc.BOOLEAN; 116 TypeDesc exceptionType = 117 TypeDesc.forClass(NoSuchPropertyException.class); 118 119 MethodInfo mi; 120 if (forRead) { 121 TypeDesc[] params = {objectType, stringType}; 122 mi = cf.addMethod 123 (Modifiers.PUBLIC, "getPropertyValue", objectType, params); 124 } else { 125 TypeDesc[] params = new TypeDesc[] { 126 objectType, stringType, objectType 127 }; 128 mi = cf.addMethod 129 (Modifiers.PUBLIC, "setPropertyValue", null, params); 130 } 131 132 mi.markSynthetic(); 133 CodeBuilder builder = new CodeBuilder(mi); 134 135 LocalVariable beanVar = builder.getParameter(0); 136 LocalVariable propertyVar = builder.getParameter(1); 137 LocalVariable valueVar; 138 if (forRead) { 139 valueVar = null; 140 } else { 141 valueVar = builder.getParameter(2); 142 } 143 144 builder.loadLocal(beanVar); 145 builder.checkCast(TypeDesc.forClass(beanType)); 146 builder.storeLocal(beanVar); 147 148 if (properties.length > 0) { 149 int[] cases = new int[hashCapacity(properties.length)]; 150 int caseCount = cases.length; 151 for (int i=0; i<caseCount; i++) { 152 cases[i] = i; 153 } 154 155 Label[] switchLabels = new Label[caseCount]; 156 Label noMatch = builder.createLabel(); 157 List [] caseMethods = caseMethods(caseCount, properties); 158 159 for (int i=0; i<caseCount; i++) { 160 List matches = caseMethods[i]; 161 if (matches == null || matches.size() == 0) { 162 switchLabels[i] = noMatch; 163 } else { 164 switchLabels[i] = builder.createLabel(); 165 } 166 } 167 168 if (properties.length > 1) { 169 builder.loadLocal(propertyVar); 170 builder.invokeVirtual(String .class.getName(), 171 "hashCode", intType, null); 172 builder.loadConstant(0x7fffffff); 173 builder.math(Opcode.IAND); 174 builder.loadConstant(caseCount); 175 builder.math(Opcode.IREM); 176 177 builder.switchBranch(cases, switchLabels, noMatch); 178 } 179 180 TypeDesc[] params = {objectType}; 182 183 for (int i=0; i<caseCount; i++) { 184 List matches = caseMethods[i]; 185 if (matches == null || matches.size() == 0) { 186 continue; 187 } 188 189 switchLabels[i].setLocation(); 190 191 int matchCount = matches.size(); 192 for (int j=0; j<matchCount; j++) { 193 BeanProperty bp = (BeanProperty)matches.get(j); 194 195 197 builder.loadConstant(bp.getName()); 198 builder.loadLocal(propertyVar); 199 builder.invokeVirtual(String .class.getName(), 200 "equals", booleanType, params); 201 202 Label notEqual; 203 204 if (j == matchCount - 1) { 205 notEqual = null; 206 builder.ifZeroComparisonBranch(noMatch, "=="); 207 } else { 208 notEqual = builder.createLabel(); 209 builder.ifZeroComparisonBranch(notEqual, "=="); 210 } 211 212 if (forRead) { 213 builder.loadLocal(beanVar); 214 builder.invoke(bp.getReadMethod()); 215 TypeDesc type = TypeDesc.forClass(bp.getType()); 216 builder.convert(type, type.toObjectType()); 217 builder.returnValue(TypeDesc.OBJECT); 218 } else { 219 builder.loadLocal(beanVar); 220 builder.loadLocal(valueVar); 221 TypeDesc type = TypeDesc.forClass(bp.getType()); 222 builder.checkCast(type.toObjectType()); 223 builder.convert(type.toObjectType(), type); 224 builder.invoke(bp.getWriteMethod()); 225 builder.returnVoid(); 226 } 227 228 if (notEqual != null) { 229 notEqual.setLocation(); 230 } 231 } 232 } 233 234 noMatch.setLocation(); 235 } 236 237 builder.newObject(exceptionType); 238 builder.dup(); 239 builder.loadLocal(propertyVar); 240 builder.loadConstant(forRead); 241 242 TypeDesc[] params = {stringType, booleanType}; 244 245 builder.invokeConstructor 246 (NoSuchPropertyException.class.getName(), params); 247 builder.throwObject(); 248 } 249 250 256 private static int hashCapacity(int min) { 257 BigInteger capacity = BigInteger.valueOf(min * 2 + 1); 258 while (!capacity.isProbablePrime(100)) { 259 capacity = capacity.add(BigInteger.valueOf(2)); 260 } 261 return capacity.intValue(); 262 } 263 264 269 private static List [] caseMethods(int caseCount, 270 BeanProperty[] props) { 271 List [] cases = new List [caseCount]; 272 273 for (int i=0; i<props.length; i++) { 274 BeanProperty prop = props[i]; 275 int hashCode = prop.getName().hashCode(); 276 int caseValue = (hashCode & 0x7fffffff) % caseCount; 277 List matches = cases[caseValue]; 278 if (matches == null) { 279 matches = cases[caseValue] = new ArrayList (); 280 } 281 matches.add(prop); 282 } 283 284 return cases; 285 } 286 287 291 private static BeanProperty[][] getBeanProperties(Class beanType) { 292 List readProperties = new ArrayList (); 293 List writeProperties = new ArrayList (); 294 295 Map map = BeanIntrospector.getAllProperties(beanType); 296 297 Iterator it = map.values().iterator(); 298 while (it.hasNext()) { 299 BeanProperty bp = (BeanProperty)it.next(); 300 if (bp.getReadMethod() != null) { 301 readProperties.add(bp); 302 } 303 if (bp.getWriteMethod() != null) { 304 writeProperties.add(bp); 305 } 306 } 307 308 BeanProperty[][] props = new BeanProperty[2][]; 309 310 props[0] = new BeanProperty[readProperties.size()]; 311 readProperties.toArray(props[0]); 312 props[1] = new BeanProperty[writeProperties.size()]; 313 writeProperties.toArray(props[1]); 314 315 return props; 316 } 317 318 protected BeanPropertyAccessor() { 319 } 320 321 323 public abstract Object getPropertyValue(Object bean, String property) 324 throws NoSuchPropertyException; 325 326 public abstract void setPropertyValue(Object bean, String property, 327 Object value) 328 throws NoSuchPropertyException; 329 330 422 } 423 | Popular Tags |