I've started to use Commons JXPath in my unit and integration tests, as a simple way to set a set of properties in a bean hierarchy.
One scenario where I can't use JXPath "cleanly" is in setting list entries.
I'd really like for JXPath to do the "obvious stuff", so that if I try to set entry "[1]" of a list (odd that they chose to use 1-based indexes), it will configure the underlying list to have at least one entry. From what I've seen, this doesn't happen, and I have to do it manually.
For instance, here's some code that I'd like to write:
JXPathContext context = JXPathContext.newContext(request);
context.setValue("foo", new Foo());
context.setValue("foo/subscriberNumber", "1234567890");
context.setValue("foo/bar", new Bar());
context.setValue("foo/bar/numbers[1]", "123"); // this fails
This does not work, for a couple of different reasons.
The first issue is that the "numbers" list property will be null. I could deal with that like this:
JXPathContext context = JXPathContext.newContext(request);
context.setValue("foo", new Foo());
context.setValue("foo/subscriberNumber", "1234567890");
context.setValue("foo/bar", new Bar());
context.setValue("foo/bar/numbers", new ArrayList<String>());
context.setValue("foo/bar/numbers[1]", "123");
Or I could register an AbstractFactory and use "createPathAndSetValue()" instead.
However, this still fails because even though the list may exist, it will have zero size and fails to access the 0th entry.
I'm forced to do something like this:
JXPathContext context = JXPathContext.newContext(request);
context.setValue("foo", new Foo());
context.setValue("foo/subscriberNumber", "1234567890");
context.setValue("foo/bar", new Bar());
context.setValue("foo/bar/numbers", Arrays.asList(""));
context.setValue("foo/bar/numbers[1]", "123");
If I have to set other entries in the list besides the first one, I'll have to init the list with more dummy entries.
I'll also feel responsible to add a comment everywhere I do this to quell the WTFs.
Is there a cleaner way to do this?
Hmmm, tricky indeed. First, digressing
That's the way XPath works. There are discussions around why, but I'm not really sure why. Lua programming language also uses 1 for arrays.
Here's some code (also in GitHub) based on your example.
When Apache Commons JXPath finds a collection, more specifically, instance of java.util.List, then it will call the set method. No matter what we try. You could try a way around with a factory, or maybe other hacks with static method calls.... but if your case is simple enough that you know beforehand how many elements your list will contain, you could use the approach above, i.e.
Initialize your list with empty elements, so that
List#set(int, E)doesn't throw an exception.Hope that helps, Bruno