This content has been marked as final. Show 1 reply
Hmm, that's a very good question. I assume you mean can you say
RULE xxx CLASS myInterface METHOD yyy AT zzz . . .
where myInterface is an interface rather than a class. The simple answer is no it will not work.
I could just leave it at that but the details are quite interesting and I would like feedback from you and other users as to what behaviour id desirable here.
With the current agent implementation I believe a rule like the one above would parse and type check ok but would never actually be applied. This is because the agent is very simple-minded at present. It only inserts a trigger for a rule if the class specified in the CLASS clause defines a method whose name matches the METHOD clause.
Note that this does not imply that a rule can only be injected once. For s start, if the CLASS clause omits any package then CLASS Foo might match org.my.Foo and org.your.Foo. So, several classes might be tested for injection. Even with a signle class there may be multiple choices.
If the METHOD name contains a signature then the agent can only inject code into at most one matching method. However, if the METHOD clause omits to specify a signature then it may inject code more than once since several methods may exist with the same name e.g. METHOD append would match both Foo.append(char) and Foo.append(String). The important thing is that in either of these cases the method must be defined by the class whose name is matched by the CLASS clause. The agent never considers methods defined by superclasses or subclasses.
Another thing to note is that the rule will only be injected if the location (AT clause) is satisfied. Obviously AT ENTRY locations always match any method but in other cases there may not be a matching location. The agent just ignores these cases (its not considered an error).
So, if the CLASS clause specifies an interface then there will never be a matching method definition because interfaces never define method implementations. The same problem can occur for abstract classes. If the method name refers to an abstract method which the abstract class does not implement then injection will never occur.
The obvious change to make to this behaviour would be to inject code into the methods of classes which implement the interface (or classes which implement the abstract method in the case where the CLASS clause matches an abstract class). The problem with this is what to do about overriding. In fact there is no need to consider interfaces or abstract classes to run up against this problem. A similar sort of situation arises with ordinary classes when the trigger method is overridden by subclasses.
Assume class A defines method foo and class B overrides method foo and that we have a rule which specifies CLASS A, METHOD foo, AT ENTRY. A trigger for the rule will be injected at the start of A.foo() but will not be injected into B.foo(). Why? Well, consider what might happen if we did do this? The rule would get triggered on entry to B.foo(). Now, if B.foo() calls super() then this will trigger the rule again at entry to A.foo(). If B.foo() does not call super() then there will be only one trigger. Each of these behaviours might be desirable for some applications but then again each one might be wrong, depending upon the application.
The current implementation requires you to write two (or more) rules if you want two (or more) triggers, one for A.foo() and one for B.foo() (and maybe C.foo(), D.foo() etc). If injection was automatically done for overriding methods then applications would always have to live with multiple triggerings. So, the current version allows you to control what happens at the cost of requiring more rules.
There is another issue with automatic injection into overriding methods which is to do with the coherence of the location specifier (the AT clause). With an AT ENTRY location then multiple triggering would happen whenever the overriding method called super(). However, with a different location clause then whether one or more triggers was injected would all depend on the details of how the parent and child methods were coded. For example, if the location was AFTER CALL println 3 (i.e. trigger the rule after the 3rd call to println) then this might match the parent but not the child or vice versa or both or neither. So, another reason for not injecting rules into subclass implementations -- at least not yet and not by default -- is that identifying how to make this behaviour coherent with all possible locations is not simple.
Now, clearly, it would be nice to be able to switch on automatic injection into overriding methods when you want it, preferably for each individual rule. One of the things I have been thinking of is adding extra syntax to allow injection into overridden methods to be supported e.g. a special form in the CLASS clause like
RULE xxx CLASS ^myClass METHOD yyy AT zzz . . .
could be used to indicate that the rule should be injected into myClass and all of its subclasses. I am still thinking about this because it raises another issue -- this time to do with performance.
With the current implementation it is very easy to decide whether a class has any matching rules. This is important because the agent is asked about every class that is loaded into the runtime. The agent indexes all currently loaded rules using the name occuring in the CLASS clause as key. So, given class org.foo.yourClass I can look up yourClass and org.foo.yourClass in the index and, rapidly decide that there is no matching rule because the only entry in the index is myClass.
Now that changes as soon as an overriding injection rule is defined. If the agent loads just one rule with using this extended syntax CLASS ^myClass then it will need to check in the index for yourClass and org.foo.yourClass and then for yourSuperClass and org.foo.yourSuperClass and so on all the way up the class tree from yourClass. It has to do this for every class in case yourSuperClass is actually myClass. 99% of the time this will be false but the extra check must be done at every class load. So, allowing this feature has significant performance implications.
Now, that brings me back to the original question about interfaces. If you want a rule to apply to interfaces then essentially this is equivalent to saying you want to inject into every method which implements a signature defined in the interface. We could use an alternative rule syntax
RULE xxx INTERFACE myInterface METHOD yyy AT zzz . . .
for this purpose. Or we could just stick with the conventional syntax and leave the agent to detect that the classs being loaded actually is an interface.
Once again this has performance implications. Given just one rule of this form the agent would now have to check the implements list of every class being loaded to see if it implements an interface called myInterface. If it does then the METHOD needs to be checked fro to see if injection is required. However, in 99% of cases this check will be unnecessary.
Then there is also the issue of how to detect and handle classes which override an interface implementation provided by a super class. For example, we could use a similar extended syntax
RULE xxx INTERFACE ^myInterface METHOD yyy AT zzz . . .
This means we now have to check whether the loaded class implements myInterface or or if one of its superclasses implements myInterface. So, the agent has yet more work to do at load time.
Now I am not saying I don't think these features are worth having. Quite to the contrary - I think it is really important to provide something along these lines. I just want to think more about how to make them available and how to make them work in a meaningful way. I'll also be very happy to hear what you would like to work and why you would like it to work that way. I don't know what is right and I don't think there is one right answer so I would be really happy for community input on this question.