Writing Reflexive Extension Functions in Java

Reflexive extension functions written in Java map the expanded name (namespace plus local-name) of the XPath function to a Java fully-qualified class name and method or field name. There are two ways of doing the mapping:

  • The namespace URI can define the class (for example java:java.io.File), and the local name then defines the method or field name (for example exists).

  • The namespace URI can be http://saxon.sf.net/java-type, and the local name then defines both the class name and the method or field name (for example java.io.File.exists).

The command line option -TJ is useful for debugging the loading of Java extensions. It gives detailed information about the methods that are examined for a possible match.

Identifying the Java Class from the Namespace URI

There are various ways a mapping from URIs to Java classes can be established. The simplest is to use a URI that identifies the Java class explicitly. The namespace URI should be java: followed by the fully-qualified class name (for example xmlns:date="java:java.util.Date"). The class must be on the classpath.

Optionally, this URI can be followed by ?void=this (for example, xmlns:date="java:java.util.Date?void=this". The effect of this option, which was introduced in Saxon 9.7, is that calling a non-static method with a declared type of void returns the object to which it is applied, rather than returning an empty sequence. This makes it easier to avoid the problems that can arise from calls on such methods being optimized away.

For example, with the help of the XPath 3.1 arrow operator, it makes it possible to chain function calls like this: let $x := c:myObject.new() => c:setLength(3) => c:setColor('blue').

The Saxon namespace URI http://saxon.sf.net/ is recognised as a special case. In most cases it causes the function to be loaded from the class com.saxonica.functions.extfn but in a few cases, such as saxon:evaluate, the function is recognized by the compiler as if it were a built-in function. The various EXSLT and EXpath namespaces are also recognized specially.

In XSLT, the system function function-available(String name) returns true if there appears to be a method available with the right name. The function also has an optional second argument to test whether there is a method with the appropriate number of arguments. However, it is not possible to test whether the arguments are of appropriate types. If the function name is "new" it returns true so long as the class is not an abstract class or interface, and so long as it has at least one constructor.

Identifying the Java constructor, method, or field

The local name used in the XPath function call determines which constructor, method, or field of the Java class is invoked. This decision (called binding) is always made at the time the XPath expression is compiled. If methods are overloaded, static type information will be used to decide between them.

  • If the local name is new, a constructor is invoked. If several constructors are available, the one that is chosen is based on the number and types of the supplied arguments.

  • In other cases, the system looks for a matching method or field based on the XPath function name as a QName.

    Firstly, the name must match. If the namespace URI is http://saxon.sf.net/java-type then the local name should be in the form com.mycorp.app.MyClass.callMethod where everything up to the last "." (here com.mycorp.app.MyClass) is taken as the full class name, and the remainder (callMethod is taken as the method or field name. But if the namespace URI is (for example) java:com.mycorp.app.MyClass, then mycorp.app.MyClass is taken as the class name, and the local name of the function call is callMethod, then the local name is taken as the Java method or field name. In both cases the name is converted to camelCase: this is done by removing any hyphen in the XPath name and forcing the immediately following character to upper case. For example the XPath function call to-string() matches the Java method toString(); but the function call can also be written as toString() if you prefer.

    Secondly, the number of arguments must match, after taking into account that (a) if the Java method expects a first argument of class net.sf.saxon.expr.XPathContext then this will be supplied automatically by the system and does not correspond to any explicit argument in the XPath function call, and (b) when invoking an instance-level (non-static) method or field, the XPath function call must supply an extra first argument, which identifies the target object for the invocation.

    A public field in a class is treated as if it were a zero-argument method, so public static fields can be accessed in the same way as public static methods, and public instance-level fields in the same way as instance-level methods.

  • If there are several matching methods, the one that is chosen is determined by comparing the static types of the supplied arguments with the required types in the method signature. See Choosing among overloaded methods.

Further information can be found in the following sections: