001/** 002 * Factory for generating walkers that are customised to particuar feature 003 * visitors. 004 * 005 * @author Matthew Pocock 006 */ 007package org.biojava.utils.walker; 008 009import java.lang.reflect.Method; 010import java.util.ArrayList; 011import java.util.Collections; 012import java.util.Comparator; 013import java.util.HashMap; 014import java.util.Iterator; 015import java.util.List; 016import java.util.Map; 017 018import org.biojava.bio.BioException; 019import org.biojava.bio.seq.FeatureFilter; 020import org.biojava.utils.AssertionFailure; 021import org.biojava.utils.ClassTools; 022import org.biojava.utils.bytecode.ByteCode; 023import org.biojava.utils.bytecode.CodeClass; 024import org.biojava.utils.bytecode.CodeException; 025import org.biojava.utils.bytecode.CodeField; 026import org.biojava.utils.bytecode.CodeMethod; 027import org.biojava.utils.bytecode.CodeUtils; 028import org.biojava.utils.bytecode.GeneratedClassLoader; 029import org.biojava.utils.bytecode.GeneratedCodeClass; 030import org.biojava.utils.bytecode.GeneratedCodeMethod; 031import org.biojava.utils.bytecode.IfExpression; 032import org.biojava.utils.bytecode.InstructionVector; 033import org.biojava.utils.bytecode.IntrospectedCodeClass; 034import org.biojava.utils.bytecode.LocalVariable; 035 036public class WalkerFactory { 037 private static Map factories = new HashMap(); 038 039 /** 040 * Make a WalkerFactory that handles a Visitor for 041 * a class of type typeClazz. 042 * 043 * @param typeClazz the Class this factory will walk over 044 */ 045 public synchronized static WalkerFactory getInstance(Class typeClazz) 046 { 047 WalkerFactory instance; 048 String typeName = typeClazz.getName(); 049 050 if ((instance = (WalkerFactory) factories.get(typeName)) == null) { 051 instance = new WalkerFactory(typeClazz); 052 factories.put(typeName, instance); 053 } 054 return instance; 055 } 056 057 public synchronized static WalkerFactory getInstance() { 058 return getInstance(FeatureFilter.class); 059 } 060 061 private final Map walkers; 062 private final GeneratedClassLoader classLoader; 063 private final List typesWithParents; 064 private final Class typeClazz; 065 066 private WalkerFactory(Class typeClazz) { 067 walkers = new HashMap(); 068 classLoader = new GeneratedClassLoader(ClassTools.getClassLoader(this)); 069 typesWithParents = new ArrayList(); 070 this.typeClazz = typeClazz; 071 } 072 073 public Class getTypeClass() 074 { 075 return typeClazz; 076 } 077 078 /** 079 * Register a type as being a 'container' class. 080 * Container classes will be scanned for methods for retrieving child 081 * instances that can be walked to. 082 * 083 * 084 * You should never need to call this. The library authors should take care 085 * of this for you. 086 * 087 * 088 * Register 'structural' classes here - those with children. 089 * 090 * @param type the Class of the type with children 091 */ 092 public synchronized void addTypeWithParent(Class type) { 093 if (!typesWithParents.contains(type)) { 094 typesWithParents.add(type); 095 } 096 } 097 098 /** 099 * Get a Walker that is customosed to a particular visitor. 100 * 101 * @param visitor the Visitor this walker will scan with 102 * @return a Walker bound to this visitor 103 * @throws BioException if the walker could not be built 104 */ 105 public synchronized Walker getWalker(Visitor visitor) 106 throws BioException { 107 Class walkerClass = (Class) walkers.get(visitor.getClass()); 108 109 if(walkerClass == null) { 110 walkers.put(visitor.getClass(), 111 walkerClass = generateWalker(visitor.getClass())); 112 } 113 114 try { 115 return (Walker) walkerClass.newInstance(); 116 } catch (InstantiationException ie) { 117 throw new AssertionFailure("Could not instantiate walker for class: " + 118 walkerClass, 119 ie); 120 } catch (IllegalAccessException iae) { 121 throw new AssertionFailure("Could not instantiate walker for class: " + 122 walkerClass, 123 iae); 124 } 125 } 126 127 private Class generateWalker(Class visitorClass) 128 throws BioException { 129 try { 130 String vcn = visitorClass.getName().replaceAll("\\$", "_"); 131 String walkerClassName = vcn + "_walker"; 132 133 CodeClass c_Visitor = IntrospectedCodeClass.forClass(Visitor.class); 134 CodeClass c_ourVisitor = IntrospectedCodeClass.forClass(visitorClass); 135 CodeClass c_Walker = IntrospectedCodeClass.forClass(Walker.class); 136 CodeClass c_WalkerBase = IntrospectedCodeClass.forClass(Object.class); 137 CodeClass c_Object = IntrospectedCodeClass.forClass(Object.class); 138 139 // make a class 140 GeneratedCodeClass walkerClass = new GeneratedCodeClass( 141 walkerClassName, 142 c_WalkerBase, 143 new CodeClass[]{c_Walker}, 144 CodeUtils.ACC_PUBLIC | CodeUtils.ACC_SUPER); 145 146 // get all visitor methods 147 Method[] methods = visitorClass.getMethods(); 148 List visitorMeths = new ArrayList(); 149 Class retClass = null; 150 151 // make a set of all handlers and get the return type used 152 // barf if the return type is not consistent 153 for(int mi = 0; mi < methods.length; mi++) { 154 Method method = methods[mi]; 155 156 Class ret = method.getReturnType(); 157 Class[] args = method.getParameterTypes(); 158 159 // is this one of our classes? 160 if(args.length > 0) { 161 Class arg0 = args[0]; 162 163 // If arg0 is inner class, strip off outer-class name to make our name 164 String methName = method.getName(); 165 String arg0Name = arg0.getName(); 166 int doli = arg0Name.lastIndexOf('$'); 167 if(doli >= 0) { 168 arg0Name = arg0Name.substring(doli+1); 169 } 170 doli = arg0Name.lastIndexOf('.'); 171 if(doli >= 0) { 172 arg0Name = arg0Name.substring(doli+1); 173 } 174 175 // drop the leading captial 176 arg0Name = arg0Name.substring(0,1).toLowerCase() + 177 arg0Name.substring(1); 178 179 // we have a naming match? 180 if(arg0Name.equals(methName)) { 181 // check argument 0 is of the type we are visiting 182 if(typeClazz.isAssignableFrom(arg0)) { 183 // we have a live one. 184 // check that the return type is good 185 if(retClass == null) { 186 retClass = ret; 187 } else { 188 if(retClass != ret) { 189 throw new BioException( 190 "Return type of all methods must agree. " + 191 "We were expecting: " + retClass.getName() + 192 " but found: " + ret.getName()); 193 } 194 } 195 196 // if there are other args, make sure they match the return type 197 for(int ai = 1; ai < args.length; ai++) { 198 Class argI = args[ai]; 199 if(argI != retClass) { 200 throw new BioException( 201 "The first argument to a handler method must be a " + 202 typeClazz.toString() + 203 "All subsequent arguments must match the return type. In: " + 204 method); 205 } 206 } 207 208 // OK - this looks like a good handler - add it to our list 209 visitorMeths.add(method); 210 } 211 } 212 } 213 } 214 215 // if retclass was never set, it's safe to make it void 216 if(retClass == null) { 217 retClass = Void.TYPE; 218 } 219 220 // sort by type - most derived first 221 Collections.sort(visitorMeths, new Comparator() { 222 public int compare(Object o1, Object o2) { 223 Method m1 = (Method) o1; Class c1 = m1.getParameterTypes()[0]; 224 Method m2 = (Method) o2; Class c2 = m2.getParameterTypes()[0]; 225 if(c1.isAssignableFrom(c2)) return +1; 226 if(c2.isAssignableFrom(c1)) return -1; 227 return 0; 228 } 229 }); 230 231 // now let's implement our dispatcher 232 CodeClass c_retClass = IntrospectedCodeClass.forClass(retClass); 233 CodeClass[] walkParams = new CodeClass[]{ c_Object, c_Visitor}; 234 235 GeneratedCodeMethod doWalk = walkerClass.createMethod( 236 "doWalk", 237 c_retClass, 238 walkParams, 239 new String[]{"target", "visitor"}, 240 CodeUtils.ACC_PUBLIC); 241 InstructionVector walkIV = new InstructionVector(); 242 LocalVariable lv_target = doWalk.getVariable("target"); 243 LocalVariable lv_visitor = doWalk.getVariable("visitor"); 244 LocalVariable lv_visitor2 = new LocalVariable(c_ourVisitor, "visitor2"); 245 walkIV.add(ByteCode.make_aload(lv_visitor)); 246 walkIV.add(ByteCode.make_checkcast(c_ourVisitor)); 247 walkIV.add(ByteCode.make_astore(lv_visitor2)); 248 249 // local variables for the return values of wrapped invocatins 250 List wrappedLVs = new ArrayList(); 251 252 253 254 // firstly, we should call And, Or, Not, etc., wrapped targets 255 // 256 // These are all listed in typesWithParents. 257 InstructionVector wrapperIV = new InstructionVector(); 258 for(Iterator fwpi = typesWithParents.iterator(); fwpi.hasNext(); ) { 259 InstructionVector wfiv = new InstructionVector(); 260 261 // find the methods that get the wrapped 262 Class filtClass = (Class) fwpi.next(); 263 CodeClass c_ourType = IntrospectedCodeClass.forClass(filtClass); 264 265 Method[] filtMeth = filtClass.getMethods(); 266 int lvi = 0; 267 268 for(int mi = 0; mi < filtMeth.length; mi++) { 269 Method m = filtMeth[mi]; 270 271 // no args, returns a typeClazz 272 if(m.getParameterTypes().length == 0 && 273 typeClazz.isAssignableFrom(m.getReturnType())) 274 { 275 CodeMethod m_getChild = IntrospectedCodeClass.forMethod(m); 276 LocalVariable lv = null; 277 278 if (c_retClass != CodeUtils.TYPE_VOID) { 279 if (lvi < wrappedLVs.size()) { 280 lv = (LocalVariable) wrappedLVs.get(lvi); 281 } else { 282 lv = new LocalVariable(c_retClass); 283 wrappedLVs.add(lv); 284 } 285 lvi++; 286 } 287 288 // res_i = this.walk( 289 // ((c_ourType) target).m_getChild(), 290 // visitor ); 291 wfiv.add(ByteCode.make_aload(doWalk.getThis())); 292 wfiv.add(ByteCode.make_aload(lv_target)); 293 wfiv.add(ByteCode.make_checkcast(c_ourType)); 294 wfiv.add(ByteCode.make_invokevirtual(m_getChild)); 295 wfiv.add(ByteCode.make_aload(lv_visitor)); 296 wfiv.add(ByteCode.make_invokevirtual(doWalk)); 297 298 if(c_retClass != CodeUtils.TYPE_VOID) { 299 wfiv.add(ByteCode.make_astore(lv)); 300 } 301 } 302 } 303 304 // if (target instanceof ourType) { 305 // do the above block 306 // } 307 wrapperIV.add(ByteCode.make_aload(lv_target)); 308 wrapperIV.add(ByteCode.make_instanceof(c_ourType)); 309 wrapperIV.add(new IfExpression(ByteCode.op_ifne, 310 wfiv, 311 CodeUtils.DO_NOTHING)); 312 } 313 314 for(Iterator lvi = wrappedLVs.iterator(); lvi.hasNext(); ) { 315 LocalVariable lv = (LocalVariable) lvi.next(); 316 walkIV.add(ByteCode.make_aconst_null()); 317 walkIV.add(ByteCode.make_astore(lv)); 318 } 319 walkIV.add(wrapperIV); 320 321 // the big if/else/if/else stack goes here - switching on the 322 // type for each method using instanceof 323 // 324 // if(filter instanceof Filt1) { 325 // viewer2.filt1( ((Filt1) filter) ); 326 // return; 327 // } 328 329 for(Iterator mi = visitorMeths.iterator(); mi.hasNext(); ) { 330 Method meth = (Method) mi.next(); 331 332 // the viewer method is invoked as: 333 // 334 // viewer2.foo( (Foo) filter, ...); 335 // 336 // if the return value is void, we just return 337 // if it is not void, we return the Bar instance it returns 338 339 CodeMethod ourMeth = IntrospectedCodeClass.forMethod(meth); 340 CodeClass c_thisFiltType = ourMeth.getParameterType(0); 341 342 InstructionVector bodyIV = new InstructionVector(); 343 bodyIV.add(ByteCode.make_aload(lv_visitor2)); 344 bodyIV.add(ByteCode.make_aload(lv_target)); 345 bodyIV.add(ByteCode.make_checkcast(c_thisFiltType)); 346 for(int ai = 1; ai < ourMeth.numParameters(); ai++) { 347 bodyIV.add(ByteCode.make_aload((LocalVariable) wrappedLVs.get(ai-1))); 348 } 349 bodyIV.add(ByteCode.make_invokevirtual(ourMeth)); 350 bodyIV.add(ByteCode.make_return(doWalk)); 351 352 // wrap this in an if(filt instanceof Foo) 353 walkIV.add(ByteCode.make_aload(lv_target)); 354 walkIV.add(ByteCode.make_instanceof(c_thisFiltType)); 355 walkIV.add(new IfExpression(ByteCode.op_ifne, 356 bodyIV, 357 CodeUtils.DO_NOTHING)); 358 } 359 360 // return void if we are void, 361 // return null if we are meant to return something but no handler was used 362 if(c_retClass == CodeUtils.TYPE_VOID) { 363 walkIV.add(ByteCode.make_return()); 364 } else { 365 walkIV.add(ByteCode.make_aconst_null()); 366 walkIV.add(ByteCode.make_areturn()); 367 } 368 369 walkerClass.setCodeGenerator(doWalk, walkIV); 370 371 // Wire doWalk to walk and if necisary create field 372 // 373 CodeField f_value = null; 374 if(c_retClass != CodeUtils.TYPE_VOID) { 375 f_value = walkerClass.createField("value", 376 CodeUtils.TYPE_OBJECT, 377 CodeUtils.ACC_PRIVATE); 378 } 379 380 { // protect us from leakey locals 381 GeneratedCodeMethod walkImpl = walkerClass.createMethod( 382 "walk", 383 CodeUtils.TYPE_VOID, 384 walkParams, 385 CodeUtils.ACC_PUBLIC); 386 InstructionVector wiIV = new InstructionVector(); 387 if (c_retClass != CodeUtils.TYPE_VOID) { 388 wiIV.add(ByteCode.make_aload(walkImpl.getThis())); 389 } 390 wiIV.add(ByteCode.make_aload(walkImpl.getThis())); 391 wiIV.add(ByteCode.make_aload(walkImpl.getVariable(0))); 392 wiIV.add(ByteCode.make_aload(walkImpl.getVariable(1))); 393 wiIV.add(ByteCode.make_invokevirtual(doWalk)); 394 if (c_retClass != CodeUtils.TYPE_VOID) { 395 wiIV.add(ByteCode.make_putfield(f_value)); 396 } 397 wiIV.add(ByteCode.make_return()); 398 399 walkerClass.setCodeGenerator(walkImpl, wiIV); 400 } 401 402 // generate the getValue() method 403 { // protect us from leakey locals 404 GeneratedCodeMethod getValue = walkerClass.createMethod( 405 "getValue", 406 CodeUtils.TYPE_OBJECT, 407 CodeUtils.EMPTY_LIST, 408 CodeUtils.ACC_PUBLIC); 409 InstructionVector gvIV = new InstructionVector(); 410 if (c_retClass == CodeUtils.TYPE_VOID) { 411 gvIV.add(ByteCode.make_aconst_null()); 412 } else { 413 gvIV.add(ByteCode.make_aload(getValue.getThis())); 414 gvIV.add(ByteCode.make_getfield(f_value)); 415 } 416 gvIV.add(ByteCode.make_areturn()); 417 walkerClass.setCodeGenerator(getValue, gvIV); 418 } 419 420 // constructor - no args, forward to SUPER, intialize field if needed 421 { // protect us from leaky locals 422 CodeMethod m_WalkerBase_init = c_WalkerBase.getConstructor(CodeUtils.EMPTY_LIST); 423 GeneratedCodeMethod init = walkerClass.createMethod("<init>", 424 CodeUtils.TYPE_VOID, 425 CodeUtils.EMPTY_LIST, 426 CodeUtils.ACC_PUBLIC); 427 InstructionVector initIV = new InstructionVector(); 428 initIV.add(ByteCode.make_aload(init.getThis())); 429 initIV.add(ByteCode.make_invokespecial(m_WalkerBase_init)); 430 if (c_retClass != CodeUtils.TYPE_VOID) { 431 initIV.add(ByteCode.make_aload(init.getThis())); 432 initIV.add(ByteCode.make_aconst_null()); 433 initIV.add(ByteCode.make_putfield(f_value)); 434 } 435 initIV.add(ByteCode.make_return()); 436 walkerClass.setCodeGenerator(init, initIV); 437 } 438 439 return classLoader.defineClass(walkerClass); 440 } catch (CodeException ce) { 441 throw new AssertionFailure("Unable to generate walker code", ce); 442 } catch (NoSuchMethodException nsme) { 443 throw new AssertionFailure("Unable to generate walker code", nsme); 444 } 445 } 446} 447