2 Replies Latest reply on Aug 17, 2005 12:27 PM by bill.burke

    Refactoring Instrumentation in AOP 2.0:  Iteration 1

    bill.burke

      Kabir,

      This is to Kabir, but anybody else is welcome to contribute to the
      discussion. For AOP 2.0 I want to rearchitect our code instrumentation to
      support a variety of things. This should be an iterative process so that we
      can meet various deadlines for the microcontainer.

      Iteration 1:

      * Should be able to define different advice chains for inherited methods.
      http://jira.jboss.com/jira/browse/JBAOP-154

      * Should be able to define different class metadata/annotation overrides for
      inherited methods.

      http://jira.jboss.com/jira/browse/JBAOP-155

      * Make the instrumentor pluggable so we can have a heavily (risky) optimized
      version of instrumentation.

      * Please rename MethodInfo to MethodJoinpoint or something like that. Also
      create Constructor and Field structurs that hold the same information as
      MethodInfo's as we will eventually want to pass these structures in as
      parameters when we implement before/after throwing, etc...

      * Per method, field, call by chains on a per instance basis. InstanceAdvisor
      API is too limited. We should be able to attach AdviceBindings on a per
      instance and on a per ClassAdvisor basis.

      The idea is that we generate a classadvisor class at instrumentation time that
      is specific for the class it is advising. This class has a
      org.jboss.aop.Domain member variable. A Domain as the same interface as an AspectManager. Except, when you
      add/remove from the Domain, you will be recalculating the interceptor
      stacks. The InstanceAdvisor is also a generated class. It extends the
      ClassAdvisor generated class. It also has its own Domain that is a child of
      the generated ClassAdvisor's domain. The InstanceAdvisor is different than
      the ClassAdvisor in that it must check to see if the ClassAdvisor's
      interceptor stacks have changed (hot deployment) and recalculate its
      own internal interceptor stacks. So, the InstanceAdvisor's Domain is a
      child of the ClassAdvisor's, the ClassAdvisor's domain is a child of the
      AspectManager.instance().

      You have full freedom to refactor, rewrite, or throw out any code you want.

      Here's example code for ideas.

      public class POJO
      {
       private static Advisor classAdvisor = new POJOAdvisor();
       protected volatile Advisor currentAdvisor = getClassAdvisor();
       protected Advisor instanceAdvisor = null;
      
       public Advisor getClassAdvisor()
       {
       return classAdvisor;
       }
      
       public boolean isInstanceAdvised()
       {
       return instanceAdvisor != null;
       }
      
       public Advisor getInstanceAdvisor()
       {
       synchronized(this)
       {
       if (instanceAdvisor == null)
       {
       instanceAdvisor = ((POJOAdvisor)currentAdvisor).createInstanceAdvisor();
       currentAdvisor = instanceAdvisor;
       }
       }
       return instanceAdvisor;
       }
      
       private int method1(int arg)
       {
       return ((POJOAdvisor)currentAdvisor).method1(this, arg);
       }
      
       private int method1$aop(int arg)
       {
       return 0;
       }
      
       private static int methodstatic()
       {
       return ((POJOAdvisor)classAdvisor).methodstatic();
       }
      
       private static int methodstatic$aop()
       {
       return 0;
       }
      
       protected static class POJOAdvisor extends Advisor
       {
       protected long versionId = 0;
       protected Domain domain = new Domain(AspectManager.instance(), false);
      
       public POJOAdvisor()
       {
       // create interceptor stack based on domain member variable.
       }
      
       public Domain getDomain()
       {
       return domain;
       }
      
       public POJOAdvisor createInstanceAdvisor()
       {
       return new POJOInstanceAdvisor(this);
       }
      
       protected MethodInfo method1$MethodInfo = new MethodInfo(/*other stuff */);
       protected int method1(POJO target, int arg)
       {
       if (method1$MethodInfo.interceptors != null)
       {
       MethodInvocation invocation = new MethodInvocation(method1$MethodInfo, method1$MethodInfo.interceptors); // put in the correct constructor
       invocation.setTargetObject(target);
       invocation.setArguments(...);
       return ((Integer)invocation.invokeNext()).intValue();
       }
       else
       {
       return target.method1$aop(arg);
       }
       }
      
       private MethodInfo methodstatic$MethodInfo = new MethodInfo(/*other stuff */);
       private int methodstatic()
       {
       if (methodstatic$MethodInfo.interceptors != null)
       {
       MethodInvocation invocation = new MethodInvocation(methodstatic$MethodInfo, methodstatic$MethodInfo.interceptors); // put in the correct constructor
       return ((Integer)invocation.invokeNext()).intValue();
       }
       else
       {
       return methodstatic$aop();
       }
       }
       }
      
       protected static class POJOInstanceAdvisor extends POJOAdvisor
       {
       protected POJOAdvisor classAdvisor;
      
       public POJOInstanceAdvisor(POJOAdvisor classAdvisor)
       {
       this.classAdvisor = classAdvisor;
       this.versionId = classAdvisor.versionId;
       }
      
       protected int method1(POJO target, int arg)
       {
       if (versionId != classAdvisor.versionId)
       {
       versionId = classAdvisor.versionId;
       rebuildInterceptors();
       }
       return super.method1(target, arg);
       }
       }
      
      }
      
      
      public class POJOChild extends POJO
      {
       private static Advisor classAdvisor = new POJOChildAdvisor(AspectManager.instance());
      
       public Advisor getClassAdvisor()
       {
       return classAdvisor;
       }
      
       public void method2()
       {
       ((POJOChildAdvisor)currentAdvisor).method2(this);
       }
      
       public void method2$aop()
       {
       // actual code
       }
      
       protected static class POJOChildAdvisor extends POJO.POJOAdvisor
       {
       protected MethodInfo method1$MethodInfo = new MethodInfo(/*other stuff */);
       protected void method2(POJOChild target)
       {
       if (method1$MethodInfo.interceptors != null)
       {
       MethodInvocation invocation = new MethodInvocation(method1$MethodInfo, method1$MethodInfo.interceptors); // put in the correct constructor
       invocation.setTargetObject(target);
       invocation.invokeNext();
       }
       else
       {
       target.method2$aop();
       }
       }
      
       public POJOAdvisor createInstanceAdvisor()
       {
       return new POJOChildInstanceAdvisor(this);
       }
      
       }
      }
      


        • 1. Re: Refactoring Instrumentation in AOP 2.0:  Iteration 1
          kabirkhan

          All the tests now pass when the advisors are generated

          I only have a Domain for the instance advisors. I kind of had it running with Domains for the class advisors too, but removed this since I saw no immediate benefit and it was easier this way. To get this to work I had to make quite a few changes to the Domain class to override methods and check with parent manager/domain etc. Also there were some issues with whether each advisor should be registered with its Domain or the AspectManager. IIRC with the Domain as-is they would be added to the Domain, so when adding bindings dynamically to the AspectManager the list of advisors would be empty and none of the advisors would get their chains updated. I worked around this by overriding stuff in Domain to add them to AspectManager which made having separate Domains for class advisors seem pointless (or I just had the wrong idea :-)

          We now have two ways of adding per instance advices:

          1) Per instance API using Domain. Generated instance advisors have a domain to which we can add stuff. It works with adding bindings and we can now have dynamic additions of full-blown aspects per instance, sorted by precedence. I'm a bit unclear on if we need to be able to add metadata on a per instance basis via this API?

          2) Old per instance API. The generated instance advisors implement InstanceAdvisor and delegate calls to the InstanceAdvisor methods to a GeneratedInstanceAdvisorMixin class. Functionally they should be exactly the same as before since it's the same code. I guess I could have simulated this using the underlying domain, but I think adding stuff before/after the main interceptor chains from the class advisors this way would be very difficult. Things added using 1) become part of the "main" interceptor chain, and things added using 2) are appended/inserted around that.

          For the instance interceptor chains to get updated for a sub class instance where it does not implement an advised method from the super class, I had to add "invoke" methods to the instance advisor for all advised joinpoints from all superclasses, as in SubPOJOInstanceAdvisor.someMethod7774272070930259974().

          I need to test the per instance stuff for caller pointcuts too.

          I've made the mixin methods work more or less the same way as for normal methods (for generated advisors), i.e. a target method, a wrapper in class calling the mixin, and a wrapper method in the advisor creating an invocation. I left the invocation unoptimised
          as in the old stuff. I still need to make this work with the new instance advisors.

          This is more or less what we now have

          package org.jboss.test.aop.dynamicgenadvisor;
          
          public class POJO
           implements Advised
          {
           private static final transient Advisor pojoadvisor$aop = new POJOAdvisor();
           protected volatile transient Advisor currentAdvisor$aop;
           protected transient InstanceAdvisor instanceAdvisor$aop;
          
           public POJO()
           {
           super();
           currentAdvisor$aop = _getAdvisor();
           System.out.println("*** POJO ctor");
           Object obj = null;
           ((POJOAdvisor)getCurrentAdvisor$aop()).aop(this);
           }
          
           public int org$jboss$test$aop$dynamicgenadvisor$POJO$someMethod$aop()
           {
           System.out.println("*** someMethod");
           return 10;
           }
          
           public void notPrepared()
           {
           System.out.println("Not prepared");
           }
          
           public Advisor _getAdvisor()
           {
           return pojoadvisor$aop;
           }
          
           protected Advisor getCurrentAdvisor$aop()
           {
           //Access current advisor via this method in case class has been serialized
           if(currentAdvisor$aop == null)
           {
           currentAdvisor$aop = _getAdvisor();
           }
           return currentAdvisor$aop;
           }
          
           public InstanceAdvisor _getInstanceAdvisor()
           {
           if(instanceAdvisor$aop == null)
           {
           synchronized(this)
           {
           if(instanceAdvisor$aop == null)
           {
           Advisor advisor = ((POJOAdvisor)pojoadvisor$aop).createInstanceAdvisor(this);
           currentAdvisor$aop = advisor;
           instanceAdvisor$aop = (InstanceAdvisor)advisor;
           }
           }
           }
           return instanceAdvisor$aop;
           }
          
           public int someMethod()
           {
           return ((POJOAdvisor)getCurrentAdvisor$aop()).someMethod7774272070930259974(this);
           }
          
           static class POJOAdvisor extends GeneratedClassAdvisor
           implements Untransformable
           {
          
           int version;
           protected static Class advisedClass = Thread.currentThread().getContextClassLoader().loadClass("org.jboss.test.aop.dynamicgenadvisor.POJO");
           protected MethodInfo aop$MethodInfo_someMethod7774272070930259974;
          
           protected AspectManager initialiseDomain()
           {
           //Class advisors use aspect manager
           return AspectManager.instance();
           }
          
           protected void rebuildInterceptors()
           {
           version++;
           super.rebuildInterceptors();
           }
          
           protected void internalRebuildInterceptors()
           {
           super.rebuildInterceptors();
           }
          
           protected void initialiseMethods()
           {
           aop$MethodInfo_someMethod7774272070930259974 = new MethodInfo(advisedClass, 0x6be3c3423709e006L, 0x8b506dc3bd620a28L, this);
           addMethodInfo(aop$MethodInfo_someMethod7774272070930259974);
           }
          
           //Similar methods to initialise Constructors, Fields etc.
          
          
           public Advisor createInstanceAdvisor(Object obj)
           {
           return new POJOInstanceAdvisor(obj, this);
           }
          
           protected int someMethod7774272070930259974(POJO pojo)
           {
           if(aop$MethodInfo_someMethod7774272070930259974.interceptors != null)
           {
           someMethod_7774272070930259974 somemethod_7774272070930259974 = new someMethod_7774272070930259974(aop$MethodInfo_someMethod7774272070930259974, aop$MethodInfo_someMethod7774272070930259974.interceptors);
           somemethod_7774272070930259974.setTargetObject(pojo);
           somemethod_7774272070930259974.typedTargetObject = pojo;
           somemethod_7774272070930259974.setAdvisor(pojo.getCurrentAdvisor$aop());
           return ((Integer)somemethod_7774272070930259974.invokeNext()).intValue();
           } else
           {
           return pojo.org$jboss$test$aop$dynamicgenadvisor$POJO$someMethod$aop();
           }
           }
          
           public POJOAdvisor()
           {
           super("org.jboss.test.aop.dynamicgenadvisor.POJO");
           AspectManager.instance().setUsesGeneratedWeaving(true);
           initialiseDomain();
           initialiseMethods();
           initialiseConstructors();
           initialiseConstructions();
           initialiseFieldReads();
           initialiseFieldWrites();
           super.initialise(advisedClass, initialiseDomain());
           initialiseCallers();
           }
          
           protected POJOAdvisor(String s)
           {
           super("$1");
           }
           }
          
           static class POJOInstanceAdvisor extends POJOAdvisor
           implements Untransformable, InstanceAdvisor
           {
          
           POJOAdvisor classAdvisor;
           protected Domain domain;
           GeneratedInstanceAdvisorMixin instanceAdvisorMixin;
           //To keep track of if instance interceptors added using old api should be readded
           protected boolean aop$MethodInfo_someMethod7774272070930259974_updated;
          
           public Domain getDomain()
           {
           return domain;
           }
          
           protected AspectManager initialiseDomain()
           {
           domain = new Domain(AspectManager.instance(), false);
           ((Domain)domain).setInheritsBindings(true);
           return domain;
           }
          
           protected void advicesUpdated()
           {
           aop$MethodInfo_someMethod7774272070930259974_updated = true;
           }
          
           //Implement all InstanceAdvisor methods and delegate to mixin
           public SimpleMetaData getMetaData()
           {
           return instanceAdvisorMixin.getMetaData();
           }
          
           public void insertInterceptor(Interceptor interceptor)
           {
           advicesUpdated();
           instanceAdvisorMixin.insertInterceptor(interceptor);
           }
          
           public void removeInterceptor(String s)
           {
           advicesUpdated();
           instanceAdvisorMixin.removeInterceptor(s);
           }
          
           ...
          
           protected void checkVersion()
           {
           if(classAdvisor.version != super.version)
           {
           super.version = classAdvisor.version;
           internalRebuildInterceptors();
           if(instanceAdvisorMixin.hasInterceptors())
           {
           advicesUpdated();
           }
           }
           }
          
           protected int someMethod7774272070930259974(POJO pojo)
           {
           checkVersion();
           if(aop$MethodInfo_someMethod7774272070930259974_updated)
           {
           super.MethodInfo_someMethod7774272070930259974.interceptors = instanceAdvisorMixin.getInterceptors(super.MethodInfo_someMethod7774272070930259974.interceptors);
           aop$MethodInfo_someMethod7774272070930259974_updated = false;
           }
           return super.someMethod7774272070930259974(pojo);
           }
          
          
           public POJOInstanceAdvisor(Object obj, POJOAdvisor pojoadvisor)
           {
           instanceAdvisorMixin = new GeneratedInstanceAdvisorMixin(obj, pojoadvisor);
           classAdvisor = pojoadvisor;
           super.version = classAdvisor.version;
           }
           }
          
           static class someMethod_7774272070930259974 extends MethodInvocation
           implements Untransformable{}
          
          }
          
          
          package org.jboss.test.aop.dynamicgenadvisor;
          
          public class SubPOJO extends POJO
           implements Advised
          {
           static class SubPOJOAdvisor extends POJO.POJOAdvisor
           implements Untransformable
           {
          
           int version;
           protected static Class advisedClass = Thread.currentThread().getContextClassLoader().loadClass("org.jboss.test.aop.dynamicgenadvisor.SubPOJO");
           protected MethodInfo aop$MethodInfo_subMethod_N_3276343010251184758;
           protected static ConstructorInfo aop$constructorInfo_0;
          
           protected void initialiseMethods()
           {
           super.initialiseMethods();
           aop$MethodInfo_subMethod_N_3276343010251184758 = new MethodInfo(advisedClass, 0xd288176417a4e58aL, 0xa49e7f9bfaceb963L, this);
           addMethodInfo(aop$MethodInfo_subMethod_N_3276343010251184758);
           }
          
           protected void initialiseConstructors()
           {
           aop$constructorInfo_0 = new ConstructorInfo(advisedClass, 0, 0xa9840fe3aefd9b08L, 0xc974387c3e94e6bcL, this);
           addConstructorInfo(aop$constructorInfo_0);
           }
          
           //Initialise the constructors, fields etc.
           protected void initialiseConstructions()
           {
           }
          
           public Advisor createInstanceAdvisor(Object obj)
           {
           return new SubPOJOInstanceAdvisor(obj, this);
           }
          
           protected void subMethod_N_3276343010251184758(SubPOJO subpojo)
           {
           if(aop$MethodInfo_subMethod_N_3276343010251184758.interceptors != null)
           {
           subMethod_N3276343010251184758 somemethod2_n3276343010251184758 = new subMethod_N3276343010251184758(aop$MethodInfo_subMethod_N_3276343010251184758, aop$MethodInfo_subMethod_N_3276343010251184758.interceptors);
           somemethod2_n3276343010251184758.setTargetObject(subpojo);
           somemethod2_n3276343010251184758.typedTargetObject = subpojo;
           somemethod2_n3276343010251184758.setAdvisor(subpojo.getCurrentAdvisor$aop());
           somemethod2_n3276343010251184758.invokeNext();
           } else
           {
           subpojo.org$jboss$test$aop$dynamicgenadvisor$SubPOJO$subMethod$aop();
           }
           }
          
           public SubPOJOAdvisor()
           {
           super("org.jboss.test.aop.dynamicgenadvisor.SubPOJO");
           AspectManager.instance().setUsesGeneratedWeaving(true);
           initialiseDomain();
           initialiseMethods();
           initialiseConstructors();
           initialiseConstructions();
           initialiseFieldReads();
           initialiseFieldWrites();
           super.initialise(advisedClass, initialiseDomain());
           initialiseCallers();
           }
          
          
           protected SubPOJOAdvisor(String s)
           {
           super(s);
           }
           }
          
           static class SubPOJOInstanceAdvisor extends SubPOJOAdvisor
           implements Untransformable, InstanceAdvisor
           {
          
           SubPOJOAdvisor classAdvisor;
           protected Domain domain;
           GeneratedInstanceAdvisorMixin instanceAdvisorMixin;
           protected boolean aop$MethodInfo_subMethod_N_3276343010251184758_updated;
           protected boolean aop$MethodInfo_someMethod7774272070930259974_updated;
          
           public Domain getDomain()
           {
           return domain;
           }
          
           protected AspectManager initialiseDomain()
           {
           domain = new Domain(AspectManager.instance(), false);
           ((Domain)domain).setInheritsBindings(true);
           return domain;
           }
          
           protected void advicesUpdated()
           {
           aop$MethodInfo_subMethod_N_3276343010251184758_updated = true;
           aop$MethodInfo_someMethod7774272070930259974_updated = true;
           aop$FieldInfo_r_i_updated = true;
           aop$FieldInfo_w_i_updated = true;
           }
          
           public SimpleMetaData getMetaData()
           {
           return instanceAdvisorMixin.getMetaData();
           }
           public Interceptor[] getInterceptors(Interceptor ainterceptor[])
           {
           return instanceAdvisorMixin.getInterceptors(ainterceptor);
           }
          
           public void insertInterceptor(Interceptor interceptor)
           {
           advicesUpdated();
           instanceAdvisorMixin.insertInterceptor(interceptor);
           }
          
           //rest of instance advisor methods
           ...
          
           protected void checkVersion()
           {
           if(classAdvisor.version != super.version)
           {
           super.version = classAdvisor.version;
           internalRebuildInterceptors();
           if(instanceAdvisorMixin.hasInterceptors())
           {
           advicesUpdated();
           }
           }
           }
          
           protected void subMethod_N_3276343010251184758(SubPOJO subpojo)
           {
           checkVersion();
           if(aop$MethodInfo_subMethod_N_3276343010251184758_updated)
           {
           super.MethodInfo_subMethod_N_3276343010251184758.interceptors = instanceAdvisorMixin.getInterceptors(super.MethodInfo_subMethod_N_3276343010251184758.interceptors);
           aop$MethodInfo_subMethod_N_3276343010251184758_updated = false;
           }
           super.subMethod_N_3276343010251184758(subpojo);
           }
          
           //Instance advisor for sub class must override this method from SUPER (i.e. POJO advisor)
           //to check if per-instance interceptors have been added
           protected int someMethod7774272070930259974(POJO pojo)
           {
           checkVersion();
           if(aop$MethodInfo_someMethod7774272070930259974_updated)
           {
           super.aop$MethodInfo_someMethod7774272070930259974.interceptors = instanceAdvisorMixin.getInterceptors(super.aop$MethodInfo_someMethod7774272070930259974.interceptors);
           aop$MethodInfo_someMethod7774272070930259974_updated = false;
           }
           return super.someMethod7774272070930259974(pojo);
           }
          
           public SubPOJOInstanceAdvisor(Object obj, SubPOJOAdvisor subpojoadvisor)
           {
           instanceAdvisorMixin = new GeneratedInstanceAdvisorMixin(obj, subpojoadvisor);
           classAdvisor = subpojoadvisor;
           super.version = classAdvisor.version;
           }
           }
          
           static class subMethod_N3276343010251184758 extends MethodInvocation
           implements Untransformable
           {}
          
           private static final transient Advisor subpojoadvisor$aop = new SubPOJOAdvisor();
          
           public SubPOJO()
           {
           }
          
           public void org$jboss$test$aop$dynamicgenadvisor$SubPOJO$subMethod$aop()
           {
           System.out.println("*** subMethod");
           }
          
           public Advisor _getAdvisor()
           {
           return subpojoadvisor$aop;
           }
          
           public InstanceAdvisor _getInstanceAdvisor()
           {
           if(super.instanceAdvisor$aop == null)
           {
           synchronized(this)
           {
           if(super.instanceAdvisor$aop == null)
           {
           Advisor advisor = ((SubPOJOAdvisor)subpojoadvisor$aop).createInstanceAdvisor(this);
           super.currentAdvisor$aop = advisor;
           super.instanceAdvisor$aop = (InstanceAdvisor)advisor;
           }
           }
           }
           return super.instanceAdvisor$aop;
           }
          
           public void subMethod()
           {
           ((SubPOJOAdvisor)getCurrentAdvisor$aop()).subMethod_N_3276343010251184758(this);
           }
          
          }
          
          




          Now for


          * Should be able to define different advice chains for inherited methods.
          http://jira.jboss.com/jira/browse/JBAOP-154



          While I see how this would work once instrumented, I am struggling a bit to find out how to do this in the case of

          public class Top
          {
           public void method();
          }
          
          public class Bottom
          {
          
          }
          
          <bind pointcut="execution(* Bottom->method())">
          
          </bind>
          


          The problem is that the MethodExecutionTransformer will never pick out a matched pointcut (Bottom.method() does not exist), so neither Top or Bottom gets the hooks built in to match the pointcut. I tried out something by copying all CtMethods from Top into Bottom before doing the matching, and keeping those that match, but that seems like a pretty inefficient way to do it? I think you mentioned something that generated advisors would solve in this regard, but I'm not sure what?




          • 2. Re: Refactoring Instrumentation in AOP 2.0:  Iteration 1
            bill.burke

             

            "kabir.khan@jboss.com" wrote:
            IIRC with the Domain as-is they would be added to the Domain, so when adding bindings dynamically to the AspectManager the list of advisors would be empty and none of the advisors would get their chains updated. I worked around this by overriding stuff in Domain to add them to AspectManager which made having separate Domains for class advisors seem pointless (or I just had the wrong idea :-)


            You modified a subclass of Domain (the overriding you're talking about?)? What if we notified each domain that a binding was added?

            Actually, maybe the AspectManager should follow the observer/observable pattern and send notifications whenever something (bind, mixin, etc..) is added. That way, the architecture would be more resilient to change and easier to extend? Your call...


            We now have two ways of adding per instance advices:

            1) Per instance API using Domain. Generated instance advisors have a domain to which we can add stuff. It works with adding bindings and we can now have dynamic additions of full-blown aspects per instance, sorted by precedence. I'm a bit unclear on if we need to be able to add metadata on a per instance basis via this API?


            Yes, we need to be able to add metadata and annotations per instance. The Microcontainer will want to add metadata per instance.


            Now for


            * Should be able to define different advice chains for inherited methods.
            http://jira.jboss.com/jira/browse/JBAOP-154



            While I see how this would work once instrumented, I am struggling a bit to find out how to do this in the case of

            public class Top
            {
             public void method();
            }
            
            public class Bottom
            {
            
            }
            
            <bind pointcut="execution(* Bottom->method())">
            
            </bind>
            


            The problem is that the MethodExecutionTransformer will never pick out a matched pointcut (Bottom.method() does not exist), so neither Top or Bottom gets the hooks built in to match the pointcut. I tried out something by copying all CtMethods from Top into Bottom before doing the matching, and keeping those that match, but that seems like a pretty inefficient way to do it? I think you mentioned something that generated advisors would solve in this regard, but I'm not sure what?


            For first iteration I think that people are going to have to "prepare" the Top class as I don't see how this could work with runtime instrumentation as Top could be loaded before Bottom. Is that a sufficient workaround?

            Bill