Array interception needed for jboss cache
kabirkhan Dec 30, 2006 4:53 PMI have commited an initial prototype of the array interception needed for POJO Cache. It is available in svn in a separate branch:
$ svn co https://svn.jboss.org/repos/jbossas/projects/aop/branches/arrays/ jboss-aop-arrays $ cd jboss-aop-arrays/build $ build.sh $ cd ../aop $ build.sh -f build-tests-jdk50.xml $ build.sh -f build-tests-jdk50.xml loadtime-ga-test -Dtest=array
The test files are in jboss-aop-arrays\aop\src\test\org\jboss\test\aop\array and configured in jboss-aop-arrays\aop\src\resources\test\array. The invocations and things needed for the array interceptions are in jboss-aop-arrays\aop\src\main\org\jboss\aop\array.
I will merge this code to trunk, once the POJO cache guys approve.
Brief details of how it works follow:
It was too hard (impossible?) to figure out who the owning object of the array is from the bytecode, since as far as the VM is concerned, the target for the Xastore and Xaload jvm instructions is the array itself. This is different from what happens when you read/write a field directly, since the putfield and getfield instructions take the object containing the fields as the target object.
So what we do now is replace ALL array access within classes caught by arrayreplacement expressions
jboss-aop.xml
<aop> <arrayreplacement class="POJO"/> <arrayreplacement expr="class(@ReplaceArrayAccess)"/> </aop>
so that the follwing
arr[5] = 100; int i = arr[5]
becomes
ArrayAdvisor.arrayWriteInt(arr, 5, 100); int i = ArrayAdvisor.arrayReadInt(arr, 5);
To determine if an array should be intercepted, if a field
1) is woven from a field() expression
and
2) is captured by an arrayreplacement expression
and
3) Is of type array or Object (since that can hold an array)
then we extend the existing hooks for the field write to register the array with an "ArrayRegistry" if the new value is an array.
class POJO{ int[] array = new array[]{1,2,3}; Object couldBeAnArray = 10; int[] notadvised = new array[]{1,2,3}; int x; } <aop> <arrayreplacement class="POJO"/> <prepare pointcut="field(* POJO->array)"/> <prepare pointcut="field(* POJO->couldBeAnArray)"/> </aop> POJO pojo = new POJO();
In the above example only POJO.array gets registered with the ArrayRegistry. If we now do:
pojo.couldBeAnArray = new int[]{1,2,3};
pojo.couldBeAnArray gets registered in the ArrayRegistry.
If the ArrayAdvisor.arrayWriteXXX() and arrayReadXXX() methods does not find the passed in array in the ArrayRegistry, it simply updates/reads the array. If the passed in array is in the ArrayRegistry, and we have interceptors defined for arrays, we create an ArrayElementReadInvocation or an ArrayElementWriteInvocation (actually a subclass depending on the type of array) and invoke on that.
Since the "target" for an array access is the array itself, the array is the TargetObject in the XXXArrayElementXXXInvocation classes. We only allow PER_VM scoped interceptors to be used for arrays, and interceptors set up apply to ALL registered arrays. They are defined using the arraybind keyword:
<aop> <interceptor class="ArrayAccessInterceptor"/> <arrayreplacement class="POJO"/> <prepare pointcut="field(* POJO->array)"/> <prepare pointcut="field(* POJO->couldBeAnArray)"/> <arraybind> <interceptor-ref name="ArrayAccessInterceptor"/> </arraybind> </aop>
I will also introduce some arrayread and arraywrite keywords, so we can differentiate whether an element is being read or written (arraybind intercepts both reads and writes).
The method ArrayRegistry.getArrayOwners(Object array), which is also accessible from ArrayElementInvocation.getArrayOwners(), allows you to get hold of all references to the array. it is returned as a list of org.jboss.aop.array.ArrayReference objects, each of which contains information about one reference to the array. It gives you access to the object containing the reference, the field this reference is stored in, and if the field is an array containing the target array within one of its elements a list of the indexes from the top of the field to get to the array.