So,
When I create a simple one-many object relationship, it works without any real problems. The main problem is when I turn on lazy loading in the parent for the child relationship. This works fine as well, for reading the objects, but when I try to delete the object, I get an exception:
java.lang.IndexOutOfBoundsException: Index: 3, Size: 3
at java.util.ArrayList.RangeCheck(ArrayList.java:508)
at java.util.ArrayList.get(ArrayList.java:320)
at org.exolab.castor.persist.RelationCollection$IteratorImp.next(Unknown Source)
at org.exolab.castor.persist.ClassMolder.markDelete(Unknown Source)
(The size three is the number of child objects in the relationship with the parent.)
I have seen other people run into this same problem, so I don't think it has to do with my particular configuration or setup. (Postgresql, Castor 0.9.4.1) Also, the fact that the code runs fine without lazy loading enabled, but crashes when it is turned on is pretty strange.
Now, interestingly, I only get the Array error when I reference the child objects (and therefore cause them to be loaded). If I simply load the parent object without doing anything that would cause the lazy objects to be loaded, it will actually delete successfully.
After tracing through code.. and admittedly, I've not read any of the Castor code before, I believe the problem is in RelationCollection.IteratorImp.
What happens is that ClassMolder.markDelete iterates through the mapping objects, it starts by tx.delete'ing all of them. Then, it goes and pulls an iterator off of the RelationCollection, which is actually an IteratorImp. At this point it starts a loop, based upon itor.hasNext().
The itor.hasNext() method does a simple check against the current index in the array against the array size to determine if there are more items. So, this will return true.
The next method however is more careful. It starts with the current arraylist index, but then checks to see if anything should be skipped with the following block:
while ( isSkipped( id ) ) {
id = _ids.get(cursor++ - _added.size());
}
The problem with this block is that all of the items have been deleted already. When isSkipped is called, the first thing it checks is to see if the object has been marked as deleted. So it returns a true on all of the items. We end up with an index too large for our ArrayList, and we end up with the exception above.
Interestingly, if I comment out this isSkipped block (which is certainly less efficient), it will succeed without a problem.
The other solution I tried was modifiying the hasMore() method in IteratorImpl to be a bit smarter about figuring out if it actually has any more or not. So, I tried checking to see if I had any non-skippable items left, as opposed to just checking the cursor offset, and using that to help determine if we are really finished. This solution also worked, and is probably a bit better since it also allows the cursor to get repositioned to the new correct location if we have valid objects remaining. And, since it would do the iteration anyway in the next() method, we are just doing it a bit earlier.. and of course, avoiding fatal errors from occuring.
So, thats what it looks like to me. If anybody else who is more familiar with the code can look at it, that would be quite helpful. I know that this has fixed my crashing problems with lazy dependent object loads. It seems like a subtle, minor bug in iteration counting, but can certainly cause a few problems.
thanks,
paul
