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