Skip to content

Commit c176665

Browse files
committed
[bugfix] Fix an issue with flattening Arrays and Lists when converting from Java types to XDM types
1 parent 816a2c6 commit c176665

File tree

4 files changed

+140
-34
lines changed

4 files changed

+140
-34
lines changed

exist-core/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,7 @@
759759
<include>src/test/resources-filtered/org/exist/xquery/JavaBindingTest.conf.xml</include>
760760
<include>src/test/java/org/exist/xquery/JavaBindingTest.java</include>
761761
<include>src/test/java/org/exist/xquery/WatchdogTest.java</include>
762+
<include>src/test/java/org/exist/xquery/XPathUtilTest.java</include>
762763
<include>src/main/java/org/exist/xquery/functions/fn/FunDocAvailable.java</include>
763764
<include>src/test/java/org/exist/xquery/functions/fn/FunXmlToJsonTest.java</include>
764765
<include>src/test/java/org/exist/xquery/functions/fn/ParsingFunctionsTest.java</include>
@@ -1753,6 +1754,7 @@
17531754
<exclude>src/test/java/org/exist/xquery/XPathOpOrSpecialCaseTest.java</exclude>
17541755
<exclude>src/test/java/org/exist/xquery/XPathQueryTest.java</exclude>
17551756
<exclude>src/main/java/org/exist/xquery/XPathUtil.java</exclude>
1757+
<exclude>src/test/java/org/exist/xquery/XPathUtilTest.java</exclude>
17561758
<exclude>src/main/java/org/exist/xquery/XQueryContext.java</exclude>
17571759
<exclude>src/test/java/org/exist/xquery/XQueryContextAttributesTest.java</exclude>
17581760
<exclude>src/test/java/org/exist/xquery/XQueryFunctionsTest.java</exclude>

exist-core/src/main/java/org/exist/xquery/Context.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,8 @@ public interface Context {
534534
* The value argument is converted into an XPath value (@see XPathUtil#javaObjectToXPath(Object)).
535535
*
536536
* @param qname the qualified name of the new variable. Any namespaces should have been declared before.
537-
* @param value a Java object, representing the fixed value of the variable.
537+
* @param value a Java object, representing the fixed value of the variable. A Java Array or List will
538+
* be interpreted as an XDM Sequence.
538539
*
539540
* @return the created Variable object.
540541
*

exist-core/src/main/java/org/exist/xquery/XPathUtil.java

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import java.math.BigInteger;
5050
import java.net.URISyntaxException;
5151
import java.util.ArrayList;
52+
import java.util.Arrays;
5253
import java.util.List;
5354
import java.util.Map;
5455

@@ -605,7 +606,7 @@ public static final Sequence javaObjectToXPath(final Object obj, final XQueryCon
605606
* directly returned, other objects are converted into the corresponding
606607
* internal types.
607608
*
608-
* @param obj The java object.
609+
* @param obj The java object. A Java Array or List will be interpreted as an XDM Sequence.
609610
* @param context XQuery context.
610611
* @param expression the expression from which the object derives.
611612
*
@@ -641,7 +642,7 @@ public static final Sequence javaObjectToXPath(final Object obj, final XQueryCon
641642
* directly returned, other objects are converted into the corresponding
642643
* internal types.
643644
*
644-
* @param obj The java object
645+
* @param obj The java object. A Java Array or List will be interpreted as an XDM Sequence.
645646
* @param context XQuery context
646647
* @param expandChars true if characters should be expanded, false otherwise.
647648
* @param expression the expression from which the object derives.
@@ -662,8 +663,8 @@ public static final Sequence javaObjectToXPath(final Object obj, final XQueryCon
662663
* @param obj The java object.
663664
* @param context XQuery context.
664665
* @param expandChars true if characters should be expanded, false otherwise.
665-
* @param listToSequence true if lists should be converted to sequences, false otherwise.
666-
* @param arrayToSequence true if arrays should be converted to sequences, false otherwise.
666+
* @param listToSequence true if Java Lists should be converted to sequences, false otherwise.
667+
* @param arrayToSequence true if Java Arrays should be converted to sequences, false otherwise.
667668
* @param expression the expression from which the object derives.
668669
*
669670
* @return the XDM sequence.
@@ -741,21 +742,8 @@ public static final Sequence javaObjectToXPath(final Object obj, final XQueryCon
741742
SerializerPool.getInstance().returnObject(streamer);
742743
}
743744

744-
} else if (listToSequence && obj instanceof List<?>) {
745-
boolean createNodeSequence = true;
746-
747-
final List<?> lst = (List<?>) obj;
748-
for (final Object next : lst) {
749-
if (!(next instanceof NodeProxy)) {
750-
createNodeSequence = false;
751-
break;
752-
}
753-
}
754-
final Sequence seq = createNodeSequence ? new AVLTreeNodeSet() : new ValueSequence(lst.size());
755-
for (final Object o : lst) {
756-
seq.add((Item) javaObjectToXPath(o, context, expandChars, listToSequence, arrayToSequence, expression));
757-
}
758-
return seq;
745+
} else if (listToSequence && obj instanceof List<?> list) {
746+
return javaArrayToXPath((List<Object>) list, context, expandChars, listToSequence, arrayToSequence, expression);
759747

760748
} else if (obj instanceof NodeList) {
761749
context.pushDocumentContext();
@@ -786,20 +774,7 @@ public static final Sequence javaObjectToXPath(final Object obj, final XQueryCon
786774
}
787775

788776
} else if (arrayToSequence && obj instanceof Object[] array) {
789-
boolean createNodeSequence = true;
790-
for (Object arrayItem : array) {
791-
if (!(arrayItem instanceof NodeProxy)) {
792-
createNodeSequence = false;
793-
break;
794-
}
795-
}
796-
797-
final Sequence seq = createNodeSequence ? new AVLTreeNodeSet() : new ValueSequence();
798-
for (final Object arrayItem : array) {
799-
seq.add((Item) javaObjectToXPath(arrayItem, context, expandChars, listToSequence, arrayToSequence, expression));
800-
}
801-
return seq;
802-
777+
return javaArrayToXPath(Arrays.asList(array), context, expandChars, listToSequence, arrayToSequence, expression);
803778
}
804779

805780
final int xdmType = javaClassToXdmType(obj.getClass());
@@ -906,6 +881,23 @@ public static final Sequence javaObjectToXPath(final Object obj, final XQueryCon
906881
}
907882
}
908883

884+
private static Sequence javaArrayToXPath(final Iterable<Object> objects, final XQueryContext context, final boolean expandChars, final boolean listToSequence, final boolean arrayToSequence, final Expression expression) throws XPathException {
885+
boolean createNodeSequence = true;
886+
for (final Object object : objects) {
887+
if (!(object instanceof NodeProxy)) {
888+
createNodeSequence = false;
889+
break;
890+
}
891+
}
892+
893+
final Sequence result = createNodeSequence ? new AVLTreeNodeSet() : new ValueSequence();
894+
for (final Object object : objects) {
895+
final Sequence seq = javaObjectToXPath(object, context, expandChars, listToSequence, arrayToSequence, expression);
896+
result.addAll(seq);
897+
}
898+
return result;
899+
}
900+
909901
/**
910902
* Converts an XMLResource into a NodeProxy.
911903
*
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Elemental
3+
* Copyright (C) 2024, Evolved Binary Ltd
4+
*
5+
6+
* https://www.evolvedbinary.com | https://www.elemental.xyz
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; version 2.1.
11+
*
12+
* This library is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public
18+
* License along with this library; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+
*/
21+
package org.exist.xquery;
22+
23+
import org.exist.xquery.value.Sequence;
24+
import org.exist.xquery.value.Type;
25+
import org.junit.jupiter.api.Test;
26+
27+
import java.util.Arrays;
28+
import java.util.List;
29+
30+
import static org.easymock.EasyMock.mock;
31+
import static org.easymock.EasyMock.replay;
32+
import static org.easymock.EasyMock.verify;
33+
import static org.junit.jupiter.api.Assertions.assertEquals;
34+
35+
public class XPathUtilTest {
36+
37+
@Test
38+
public void listStringsToSequenceStrings() throws XPathException {
39+
final XQueryContext mockContext = mock(XQueryContext.class);
40+
41+
final List<String> strings = Arrays.asList("hello", "goodbye");
42+
43+
replay(mockContext);
44+
final Sequence result = XPathUtil.javaObjectToXPath(strings, mockContext);
45+
assertEquals(strings.size(), result.getItemCount());
46+
for (int i = 0; i < strings.size(); i++) {
47+
assertEquals(Type.STRING, result.itemAt(i).getType());
48+
assertEquals(strings.get(i), result.itemAt(i).getStringValue());
49+
}
50+
verify(mockContext);
51+
}
52+
53+
@Test
54+
public void arrayStringsToSequenceStrings() throws XPathException {
55+
final XQueryContext mockContext = mock(XQueryContext.class);
56+
57+
final String[] strings = { "hello", "goodbye" };
58+
59+
replay(mockContext);
60+
final Sequence result = XPathUtil.javaObjectToXPath(strings, mockContext);
61+
assertEquals(strings.length, result.getItemCount());
62+
for (int i = 0; i < strings.length; i++) {
63+
assertEquals(Type.STRING, result.itemAt(i).getType());
64+
assertEquals(strings[i], result.itemAt(i).getStringValue());
65+
}
66+
verify(mockContext);
67+
}
68+
69+
@Test
70+
public void listListStringsToSequenceStrings() throws XPathException {
71+
final XQueryContext mockContext = mock(XQueryContext.class);
72+
73+
final List<List<String>> list = Arrays.asList(
74+
List.of("hello"),
75+
List.of("goodbye", "see you again soon")
76+
);
77+
78+
replay(mockContext);
79+
final Sequence result = XPathUtil.javaObjectToXPath(list, mockContext);
80+
assertEquals(3, result.getItemCount());
81+
for (int i = 0; i < list.size(); i++) {
82+
final List<String> subList = list.get(i);
83+
for (int j = 0; j < subList.size(); j++) {
84+
assertEquals(Type.STRING, result.itemAt(i + j).getType());
85+
assertEquals(subList.get(j), result.itemAt(i + j).getStringValue());
86+
}
87+
}
88+
verify(mockContext);
89+
}
90+
91+
@Test
92+
public void arrayArrayStringsToSequenceStrings() throws XPathException {
93+
final XQueryContext mockContext = mock(XQueryContext.class);
94+
95+
final Object[] array = new Object[2];
96+
array[0] = new String[]{ "hello" };
97+
array[1] = new String[]{ "goodbye", "see you again soon" };
98+
99+
replay(mockContext);
100+
final Sequence result = XPathUtil.javaObjectToXPath(array, mockContext);
101+
assertEquals(3, result.getItemCount());
102+
for (int i = 0; i < array.length; i++) {
103+
final String[] subArray = (String[]) array[i];
104+
for (int j = 0; j < subArray.length; j++) {
105+
assertEquals(Type.STRING, result.itemAt(i + j).getType());
106+
assertEquals(subArray[j], result.itemAt(i + j).getStringValue());
107+
}
108+
}
109+
verify(mockContext);
110+
}
111+
}

0 commit comments

Comments
 (0)