Bug #2328
closedReflexive extension functions using java.util.List
100%
Description
Consider the code
Q{java:java.util.List}size( Q{java:myClass}makeEmptyList() )
where makeEmptyList is a Java method that returns "new ArrayList()".
Here List.size() is a non-static ("instance-level") method, and the documentation says that the first argument must be a "Java object", that is a special kind of Item that wraps a Java object, and which can only be obtained from extension functions. This example doesn't work, because although makeEmptyList() returns an instance of java.util.List, this is immediately converted to an XDM empty sequence: so what is passed to List.size() is not a (wrapped) Java object, but an empty sequence.
In previous releases (I've been looking at 9.4) Saxon was actually more liberal than the documentation implied. Although makeEmptyList() returned an empty sequence, the code for calling List.size() would convert this back to a Java object, knowing that a Java object was the required type. In fact the code to do this conversion is still present in 9.6, but it doesn't get this far: the call fails static type checking, because the static type checking follows the rules in the documentation, and requires that the supplied argument is a Java object, without allowing for any conversion.
Updated by Michael Kay over 9 years ago
The documentation for both 9.4 and 9.6 suggests that this shouldn't work. It was working in 9.4 more or less by chance; it stops working in 9.6 because there is stronger static type checking, which is enforcing what the documentation says.
I can probably get it to work in 9.6 but I'm not all that sure it's a good idea. Trying to work with the dual personality of java.util.List as both an external Java object to be manipulated in the same way as other "external" java objects, and at the same time as the Java equivalent to an XPath sequence, is never going to be completely satisfactory, and making these tests work will almost certainly break something else.
I was hoping it might work if the type of the variable is declared explicitly:
<xsl:variable name="theList" select="jf:getEmptyList()" as="jt:java.util.List" xmlns:jt="http://saxon.sf.net/java-type"/>
as this seems a reasonable way for the user to declare that they want $theList to be an external Java object of type java.util.List, rather than an XDM empty sequence. Unfortunately this doesn't do the trick in 9.6: conversion of the result of a function call is not influenced by the required type of the context where the function call appears. But I would like to explore it as a possible way forward.
Updated by Michael Kay over 9 years ago
I have got this to work (with the declared type on the variable) by adding logic to the function conversion rules (TypeChecker.java): if the required type is a Java external object type, and the expression is a Java external function call, then it checks that the return type of the Java call is assignable to the required type, and if so, suppresses the conversion to an XDM value, replacing it with a simple wrapping operation. This may also work (to be tested) without the variable, where the required type is derived directly from the argument types of an outer function call.
Updated by Michael Kay over 9 years ago
This approach doesn't work with another analagous example where the call is to
Q{java:java.lang.String}split( "x,y,z" )
Again this violates the rule that the first argument of an instance call must be a wrapped Java object, but 9.4 was doing the conversion (here from xs:string to jt:java.lang.String) implicitly.
Updated by Michael Kay over 9 years ago
- Category set to Saxon extensions
- Status changed from New to Resolved
I have applied two patches (on the 9.6 and 9.7 branches).
(a) In the code for JavaExtensionFunctionCall, during the typeCheck method for calls to instance-level functions, if the static type of the expression supplied as the first argument is not JavaExternalObjectType, then a converter is allocated, which uses the standard conversion rules to attempt to convert the value of the supplied argument to an external Java object. This is likely to succeed only for classes such as java.util.List, java.lang.String, java.lang.Double, java.util.Map etc that correspond directly to XDM types.
(b) TypeChecker.staticTypeCheck() now handles the case where the required item type is a Java external object type, and the supplied expression is a Java extension function call; it checks whether the "native" (unconverted) type of the result of the Java method matches the required type, and if so it suppresses any result conversion that would normally take place. So for example if the return type of a Java method is java.util.ArrayList, and the required type is jt:java.util.List, then the return value will be used "as is" rather than being converted to an XDM Sequence.
These require documentation changes.
Updated by O'Neil Delpratt over 9 years ago
- Status changed from Resolved to Closed
- % Done changed from 0 to 100
- Fixed in version set to 9.6.0.5
Bug fix applied in the Saxon 9.6.0.5 maintenance release.
Updated by O'Neil Delpratt almost 9 years ago
- Sprint/Milestone set to 9.6.0.5
- Applies to branch 9.6 added
- Fix Committed on Branch 9.6 added
- Fixed in Maintenance Release 9.6.0.5 added
Please register to edit this issue