Project

Profile

Help

Bug #4659

closed

Cannot convert value class net.sf.saxon.ma.map.HashTrieMap of type map(xs:string, xs:string) to class java.util.Map

Added by Fady Moussallam over 3 years ago. Updated over 3 years ago.

Status:
Closed
Priority:
Normal
Assignee:
Category:
Saxon extensions
Sprint/Milestone:
-
Start date:
2020-07-30
Due date:
% Done:

0%

Estimated time:
Legacy ID:
Applies to branch:
Fix Committed on Branch:
Fixed in Maintenance Release:
Platforms:

Description

Hello,

We are hitting an issue with Saxon PE 9.7.0-21 where java code is invoked from XQuery and gets passed a HashTrieMap where a regular Map was expected.

Here is code to reproduce this:

package org.talend.transform.xquery.test;

import java.util.HashMap;
import java.util.Map;

import org.junit.Test;

import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.s9api.XQueryCompiler;
import net.sf.saxon.s9api.XQueryEvaluator;
import net.sf.saxon.s9api.XQueryExecutable;

public class Tdm8225Test {
	
	@Test
	public void t() throws Exception {
		
		String script = "xquery version \"1.0\";\n" + 
				"declare namespace tdm8225Test = \"java:org.talend.transform.xquery.test.Tdm8225Test\";\n" + 
				"\n" + 
				"let $xml := <rootElement>\n" + 
				"    <elementA>a</elementA>\n" + 
				"    <elementA>b</elementA>\n" + 
				"    <elementA>b</elementA>\n" + 
				"    <elementA>b</elementA>\n" + 
				"    <elementA>a</elementA>\n" + 
				"    <elementA>c</elementA>\n" + 
				"    <elementA>c</elementA>\n" + 
				"    <elementA>c</elementA>\n" + 
				"    <elementA>c</elementA>\n" + 
				"    <elementA>c</elementA>\n" + 
				"</rootElement>\n" + 
				"\n" + 
				"return for $key in $xml/elementA\n" + 
				"  return tdm8225Test:getValue(tdm8225Test:getMap(), $key)";
		
        Processor proc = new Processor(true);
        ActivateSaxon.activate(proc);
        XQueryCompiler comp = proc.newXQueryCompiler();
        XQueryExecutable exp = comp.compile(script);
        Serializer out = proc.newSerializer();
        out.setOutputProperty(Serializer.Property.METHOD, "xml");
        out.setOutputProperty(Serializer.Property.INDENT, "yes");
        out.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, "yes");
        out.setOutputStream(System.out);
        
        XQueryEvaluator eval = exp.load();
        eval.run(out);

	}

	public static Map<String, String> getMap() {
		Map<String, String> myMap = new HashMap<>();
		myMap.put("City","Bonn");
		return myMap;
	}
	
	public static String getValue(Map<String, String> map, String key) {
		return map.get(key);
	}
}

Actions #1

Updated by Michael Kay over 3 years ago

The rules for this have changed a little in successive releases and I'm not sure exactly what the position was with 9.7, which is now quite old. But the 9.7 documentation states at http://saxonica.com/documentation9.7/index.html#!extensibility/functions/function-result

If the result is any other Java object (including null), it is returned as a "wrapped Java object". It is also possible to force the result to be returned as a wrapped Java object by making the function call in the initializer of a variable with an appropriate type declaration. The required type for a wrapped object of Java class java.util.Map is written using the QName jt:java.util.Map, where the namespace prefix jt represents the namespace http://saxon.sf.net/java-type.

Admittedly the rule "If the result is any other Java object (including null), it is returned as a "wrapped Java object"." would seem to apply here, because java.util.Map is not included in the preceding list. But I suspect that's an oversight, otherwise java.util.Map wouldn't have been chosen as the example to use here.

I think you don't want the result of your getMap() function to be converted to an XDM map, and this paragraph suggests you can achieve this binding the result of getMap() to a variable of type jt:java.util.Map. Please give this a try.

Actions #2

Updated by Fady Moussallam over 3 years ago

Hello and thank you very much for the quick reply.

First of all you are right, I tried 9.9.1-7 which works without a problem. So this is an issue only with older versions such as 9.7.0-21.

Unfortunately we are just now starting our migration to 9.9 which is going to take a while so we have to find a solution based on 9.7.

I tried this:

declare variable $map as jt:java.util.Map := tdm8225Test:getMap();

But got

  XPTY0004: Required item type of value of variable $map is
  Q{http://saxon.sf.net/java-type}java.util.Map; supplied value has item type map(xs:string,
  xs:string)

Maybe I misunderstood what you were suggesting?

Actions #3

Updated by Michael Kay over 3 years ago

This issue prompted me to take a fresh look at both the code and documentation for Java-to-XDM and XDM-to-Java conversion. The code and documentation had got rather out of sync, and I hope I've now fixed that.

In comment #1, I think I was looking at the wrong part of the documentation (caused by not reading your code carefully enough). We're concerned here with XDM-to-Java conversion, documented at http://www.saxonica.com/documentation/index.html#!extensibility/functions/converting-args/converting-arguments

This doesn't describe any conversion where the expected type is java.util.Map. As far as I can see, neither the code nor the documentation suggests that there is an automatic conversion from XDM maps to Java Maps, and this is true of all releases. I think your test which appeared to show that such a conversion exists in 9.9 must have been mistaken.

Actions #4

Updated by Michael Kay over 3 years ago

  • Status changed from New to In Progress
Actions #5

Updated by Michael Kay over 3 years ago

Sorry, I was only looking at half the issue. Your code does

return tdm8225Test:getValue(tdm8225Test:getMap(), $key)"

and I was looking at the getValue() part.

Your getMap() function returns a Java Map. In 9.9 there is no special conversion for a Java Map, so it is wrapped as an external object. Your getValue() function expects a Java Map, and the supplied value is an XDM "external object" wrapping a Java Map, so it simply gets unwrapped and passed to the function. It never gets converted to an XDM map.

In 9.7, in Saxon-PE and Saxon-EE only, the Java-to-XDM conversion implicitly converted a Java Map to an XDM Map, but there was no corresponding conversion in the opposite direction. So your getMap() function produced an XDM Map item, which could not be converted back to a Java Map when calling the getValue() method.

If I remember right, we changed this (a) because it wasn't good that Saxon-HE behaved differently from -PE and -EE, (b) because it's good for the conversions to round-trip wherever possible, (c) because converting an XDM map to a Java map has edge case complications involving keys that are duplicates in Java but not in XDM.

One workaround for 9.7 would be for your getMap() to do the wrapping: replace return myMap; with return new net.sf.saxon.value.ObjectValue(myMap);

Actions #6

Updated by Michael Kay over 3 years ago

  • Category set to Saxon extensions
  • Status changed from In Progress to Closed
  • Assignee set to Michael Kay

Closing this as the software is working as designed and I don't propose to make any software changes in response to the issue.

Actions #7

Updated by Fady Moussallam over 3 years ago

Thank you Michael, your workaround works fine.

Please register to edit this issue

Also available in: Atom PDF