View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jxpath.ri.model;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Locale;
29  
30  import org.apache.commons.jxpath.AbstractFactory;
31  import org.apache.commons.jxpath.AbstractJXPathTest;
32  import org.apache.commons.jxpath.ClassFunctions;
33  import org.apache.commons.jxpath.JXPathContext;
34  import org.apache.commons.jxpath.NestedTestBean;
35  import org.apache.commons.jxpath.Pointer;
36  import org.apache.commons.jxpath.ri.QName;
37  import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
38  import org.apache.commons.jxpath.ri.compiler.TestFunctions;
39  import org.apache.commons.jxpath.ri.model.beans.PropertyOwnerPointer;
40  import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
41  import org.apache.commons.jxpath.ri.model.dynabeans.DynaBeanModelTest;
42  import org.junit.jupiter.api.BeforeEach;
43  import org.junit.jupiter.api.Test;
44  
45  /**
46   * Abstract superclass for Bean access with JXPath.
47   */
48  public abstract class AbstractBeanModelTest extends AbstractJXPathTest {
49  
50      private JXPathContext context;
51  
52      protected abstract Object createContextBean();
53  
54      protected abstract AbstractFactory getAbstractFactory();
55  
56      private int relativeProperty(final PropertyPointer holder, final int offset) {
57          final String[] names = holder.getPropertyNames();
58          for (int i = 0; i < names.length; i++) {
59              if (names[i].equals("integers")) {
60                  return i + offset;
61              }
62          }
63          return -1;
64      }
65  
66      @Override
67      @BeforeEach
68      public void setUp() {
69  //        if (context == null) {
70          context = JXPathContext.newContext(createContextBean());
71          context.setLocale(Locale.US);
72          context.setFactory(getAbstractFactory());
73  //        }
74      }
75  
76      @Test
77      public void testAttributeLang() {
78          assertXPathValue(context, "@xml:lang", "en-US");
79          assertXPathValue(context, "count(@xml:*)", Double.valueOf(1));
80          assertXPathValue(context, "lang('en')", Boolean.TRUE);
81          assertXPathValue(context, "lang('fr')", Boolean.FALSE);
82      }
83  
84      /**
85       * Testing the pseudo-attribute "name" that java beans objects appear to have.
86       */
87      @Test
88      public void testAttributeName() {
89          assertXPathValue(context, "nestedBean[@name = 'int']", Integer.valueOf(1));
90          assertXPathPointer(context, "nestedBean[@name = 'int']", "/nestedBean/int");
91      }
92  
93      @Test
94      public void testAxisAncestor() {
95          // ancestor::
96          assertXPathValue(context, "int/ancestor::root = /", Boolean.TRUE);
97          assertXPathValue(context, "count(beans/name/ancestor-or-self::node())", Double.valueOf(5));
98          assertXPathValue(context, "beans/name/ancestor-or-self::node()[3] = /", Boolean.TRUE);
99      }
100 
101     @Test
102     public void testAxisAttribute() {
103         // Attributes are just like children to beans
104         assertXPathValue(context, "count(@*)", Double.valueOf(21.0));
105         // Unknown attribute
106         assertXPathValueLenient(context, "@foo", null);
107     }
108 
109     @Test
110     public void testAxisChild() {
111         assertXPathValue(context, "boolean", Boolean.FALSE);
112         assertXPathPointer(context, "boolean", "/boolean");
113         assertXPathPointerIterator(context, "boolean", list("/boolean"));
114         // Count elements in a child collection
115         assertXPathValue(context, "count(set)", Double.valueOf(3));
116 //        assertXPathValue(context,"boolean/class/name", "java.lang.Boolean");
117         // Child with namespace - should not find any
118         assertXPathValueIterator(context, "foo:boolean", list());
119         // Count all children with a wildcard
120         assertXPathValue(context, "count(*)", Double.valueOf(21));
121         // Same, constrained by node type = node()
122         assertXPathValue(context, "count(child::node())", Double.valueOf(21));
123     }
124 
125     @Test
126     public void testAxisChildNestedBean() {
127         // Nested bean
128         assertXPathValue(context, "nestedBean/name", "Name 0");
129         assertXPathPointer(context, "nestedBean/name", "/nestedBean/name");
130         assertXPathPointerIterator(context, "nestedBean/name", list("/nestedBean/name"));
131     }
132 
133     @Test
134     public void testAxisChildNestedCollection() {
135         assertXPathValueIterator(context, "integers", list(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4)));
136         assertXPathPointer(context, "integers", "/integers");
137         assertXPathPointerIterator(context, "integers", list("/integers[1]", "/integers[2]", "/integers[3]", "/integers[4]"));
138     }
139 
140     @Test
141     public void testAxisDescendant() {
142         // descendant::
143         assertXPathValue(context, "count(descendant::node())", Double.valueOf(65));
144         // Should not find any descendants with name root
145         assertXPathValue(context, "count(descendant::root)", Double.valueOf(0));
146         assertXPathValue(context, "count(descendant::name)", Double.valueOf(7));
147     }
148 
149     @Test
150     public void testAxisDescendantOrSelf() {
151         // descendant-or-self::
152         assertXPathValueIterator(context, "descendant-or-self::name", set("Name 1", "Name 2", "Name 3", "Name 6", "Name 0", "Name 5", "Name 4"));
153         // Same - abbreviated syntax
154         assertXPathValueIterator(context, "//name", set("Name 1", "Name 2", "Name 3", "Name 6", "Name 0", "Name 5", "Name 4"));
155         // See that it actually finds self
156         assertXPathValue(context, "count(descendant-or-self::root)", Double.valueOf(1));
157         // Combine descendant-or-self:: and and self::
158         assertXPathValue(context, "count(nestedBean//.)", Double.valueOf(7));
159         // Combine descendant-or-self:: and and self::name
160         assertXPathValue(context, "count(//self::beans)", Double.valueOf(2));
161         // Count all nodes in the tree
162         assertXPathValue(context, "count(descendant-or-self::node())", Double.valueOf(66));
163     }
164 
165     @Test
166     public void testAxisFollowing() {
167         // following::
168         assertXPathValue(context, "count(nestedBean/strings[2]/following::node())", Double.valueOf(21));
169         assertXPathValue(context, "count(nestedBean/strings[2]/following::strings)", Double.valueOf(7));
170     }
171 
172     @Test
173     public void testAxisFollowingSibling() {
174         // following-sibling::
175         assertXPathValue(context, "count(/nestedBean/following-sibling::node())", Double.valueOf(8));
176         assertXPathValue(context, "count(/nestedBean/following-sibling::object)", Double.valueOf(1));
177         // Combine parent:: and following-sibling::
178         assertXPathValue(context, "count(/nestedBean/boolean/../following-sibling::node())", Double.valueOf(8));
179         assertXPathValue(context, "count(/nestedBean/boolean/../following-sibling::object)", Double.valueOf(1));
180         // Combine descendant:: and following-sibling::
181         assertXPathValue(context, "count(/descendant::boolean/following-sibling::node())", Double.valueOf(53));
182         assertXPathValue(context, "count(/descendant::boolean/following-sibling::name)", Double.valueOf(7));
183     }
184 
185     @Test
186     public void testAxisParent() {
187         // parent::
188         assertXPathValue(context, "count(/beans/..)", Double.valueOf(1));
189         assertXPathValue(context, "count(//..)", Double.valueOf(9));
190         assertXPathValue(context, "count(//../..)", Double.valueOf(2));
191         assertXPathValueIterator(context, "//parent::beans/name", list("Name 1", "Name 2"));
192     }
193 
194     @Test
195     public void testAxisPreceding() {
196         // preceding::
197         assertXPathValue(context, "count(beans[2]/int/preceding::node())", Double.valueOf(8));
198         assertXPathValue(context, "count(beans[2]/int/preceding::boolean)", Double.valueOf(2));
199     }
200 
201     @Test
202     public void testAxisPrecedingSibling() {
203         // preceding-sibling::
204         assertXPathValue(context, "count(/boolean/preceding-sibling::node())", Double.valueOf(2));
205         assertXPathValue(context, "count(/nestedBean/int/../preceding-sibling::node())", Double.valueOf(12));
206         assertXPathValue(context, "count(/descendant::int/preceding-sibling::node())", Double.valueOf(10));
207     }
208 
209     @Test
210     public void testAxisSelf() {
211         // self::
212         assertXPathValue(context, "self::node() = /", Boolean.TRUE);
213         assertXPathValue(context, "self::root = /", Boolean.TRUE);
214     }
215 
216     @Test
217     public void testBooleanPredicate() {
218         // use child axis
219         // bean[1]/int = 1
220         // bean[2]/int = 3
221         assertXPathValue(context, "beans[int > 2]/name", "Name 2");
222         assertXPathValueIterator(context, "beans[int > 2]/name", list("Name 2"));
223         assertXPathValueIterator(context, "beans[int >= 1]/name", list("Name 1", "Name 2"));
224         assertXPathValueIterator(context, "beans[int < 2]/name", list("Name 1"));
225         assertXPathValueIterator(context, "beans[int <= 3]/name", list("Name 1", "Name 2"));
226         assertXPathValueIterator(context, "beans[1]/strings[string-length() = 8]", list("String 1", "String 2", "String 3"));
227         // use some fancy axis and the child axis in the predicate
228         assertXPathValueIterator(context, "//self::node()[name = 'Name 0']/name", list("Name 0"));
229         // use context-dependent function in the predicate
230         assertXPathValue(context, "beans/strings[name(.)='strings'][2]", "String 2");
231         // use context-independent function in the predicate
232         assertXPathValueIterator(context, "//self::node()[name(.) = concat('n', 'a', 'm', 'e')]",
233                 list("Name 1", "Name 2", "Name 3", "Name 6", "Name 0", "Name 5", "Name 4"));
234         assertXPathValueIterator(context, "integers[position()<3]", list(Integer.valueOf(1), Integer.valueOf(2)));
235         context.getVariables().declareVariable("temp", context.getValue("beans"));
236         assertXPathValueIterator(context, "$temp[int < 2]/int", list(Integer.valueOf(1)));
237     }
238 
239     @Test
240     public void testCoreFunctions() {
241         assertXPathValue(context, "boolean(boolean)", Boolean.TRUE);
242         assertXPathValue(context, "boolean(boolean = false())", Boolean.TRUE);
243         assertXPathValue(context, "boolean(integers[position() < 3])", Boolean.TRUE);
244         assertXPathValue(context, "boolean(integers[position() > 4])", Boolean.FALSE);
245         assertXPathValue(context, "sum(integers)", Double.valueOf(10));
246         assertXPathValueAndPointer(context, "integers[last()]", Integer.valueOf(4), "/integers[4]");
247         assertXPathValueAndPointer(context, "//strings[last()]", "String 3", "/beans[1]/strings[3]");
248     }
249 
250     @Test
251     public void testCreatePath() {
252         context.setValue("nestedBean", null);
253         // Calls factory.createObject(..., TestBean, "nestedBean")
254         assertXPathCreatePath(context, "/nestedBean/int", Integer.valueOf(1), "/nestedBean/int");
255         assertThrows(Exception.class,
256                 () -> assertXPathCreatePath(context, "/nestedBean/beans[last() + 1]", Integer.valueOf(1), "/nestedBean/beans[last() + 1]"),
257                 "Exception thrown on invalid path for creation");
258     }
259 
260     @Test
261     public void testCreatePathAndSetValue() {
262         context.setValue("nestedBean", null);
263         // Calls factory.createObject(..., TestBean, "nestedBean")
264         assertXPathCreatePathAndSetValue(context, "/nestedBean/int", Integer.valueOf(2), "/nestedBean/int");
265     }
266 
267     @Test
268     public void testCreatePathAndSetValueCreateBeanExpandCollection() {
269         context.setValue("nestedBean", null);
270         // Calls factory.createObject(..., TestBean, "nestedBean")
271         // Calls factory.createObject(..., nestedBean, "strings", 2)
272         assertXPathCreatePathAndSetValue(context, "/nestedBean/strings[2]", "Test", "/nestedBean/strings[2]");
273     }
274 
275     @Test
276     public void testCreatePathAndSetValueExpandExistingCollection() {
277         // Another, but the collection already exists
278         assertXPathCreatePathAndSetValue(context, "/beans[3]/int", Integer.valueOf(2), "/beans[3]/int");
279     }
280 
281     @Test
282     public void testCreatePathAndSetValueExpandNewCollection() {
283         context.setValue("beans", null);
284         // Calls factory.createObject(..., testBean, "beans", 2),
285         // then factory.createObject(..., testBean, "beans", 2)
286         assertXPathCreatePathAndSetValue(context, "/beans[2]/int", Integer.valueOf(2), "/beans[2]/int");
287     }
288 
289     @Test
290     public void testCreatePathCreateBeanExpandCollection() {
291         context.setValue("nestedBean", null);
292         // Calls factory.createObject(..., TestBean, "nestedBean")
293         // Calls factory.createObject(..., nestedBean, "strings", 2)
294         assertXPathCreatePath(context, "/nestedBean/strings[2]", "String 2", "/nestedBean/strings[2]");
295     }
296 
297     @Test
298     public void testCreatePathExpandExistingCollection() {
299         // Calls factory.createObject(..., TestBean, "integers", 5)
300         // to expand collection
301         assertXPathCreatePathAndSetValue(context, "/integers[5]", Integer.valueOf(3), "/integers[5]");
302     }
303 
304     @Test
305     public void testCreatePathExpandExistingCollectionAndSetProperty() {
306         // Another, but the collection already exists
307         assertXPathCreatePath(context, "/beans[3]/int", Integer.valueOf(1), "/beans[3]/int");
308     }
309 
310     @Test
311     public void testCreatePathExpandNewCollection() {
312         context.setValue("beans", null);
313         // Calls factory.createObject(..., testBean, "beans", 2),
314         // then factory.createObject(..., testBean, "beans", 2)
315         assertXPathCreatePath(context, "/beans[2]/int", Integer.valueOf(1), "/beans[2]/int");
316     }
317 
318     @Test
319     public void testDocumentOrder() {
320         assertDocumentOrder(context, "boolean", "int", -1);
321         assertDocumentOrder(context, "integers[1]", "integers[2]", -1);
322         assertDocumentOrder(context, "integers[1]", "integers[1]", 0);
323         assertDocumentOrder(context, "nestedBean/int", "nestedBean", 1);
324         assertDocumentOrder(context, "nestedBean/int", "nestedBean/strings", -1);
325         assertDocumentOrder(context, "nestedBean/int", "object/int", -1);
326     }
327 
328     @Test
329     public void testIndexPredicate() {
330         assertXPathValue(context, "integers[2]", Integer.valueOf(2));
331         assertXPathPointer(context, "integers[2]", "/integers[2]");
332         assertXPathPointerIterator(context, "integers[2]", list("/integers[2]"));
333         assertXPathValue(context, "beans[1]/name", "Name 1");
334         assertXPathPointer(context, "beans[1]/name", "/beans[1]/name");
335         assertXPathValueIterator(context, "beans[1]/strings", list("String 1", "String 2", "String 3"));
336         assertXPathValueIterator(context, "beans/strings[2]", list("String 2", "String 2"));
337         // Find the first match
338         assertXPathValue(context, "beans/strings[2]", "String 2");
339         // Indexing in a set collected from a UnionContext
340         assertXPathValue(context, "(beans/strings[2])[1]", "String 2");
341     }
342 
343     private void testIndividual(final int relativePropertyIndex, final int offset, final boolean useStartLocation, final boolean reverse, final int expected) {
344         final PropertyOwnerPointer root = (PropertyOwnerPointer) NodePointer.newNodePointer(new QName(null, "root"), createContextBean(), Locale.getDefault());
345         NodeIterator it;
346         PropertyPointer start = null;
347         if (useStartLocation) {
348             start = root.getPropertyPointer();
349             start.setPropertyIndex(relativeProperty(start, relativePropertyIndex));
350             start.setIndex(offset);
351         }
352         it = root.childIterator(new NodeNameTest(new QName(null, "integers")), reverse, start);
353         int size = 0;
354         while (it.setPosition(it.getPosition() + 1)) {
355             size++;
356         }
357         assertEquals(expected, size, "ITERATIONS: Individual, relativePropertyIndex=" + relativePropertyIndex + ", offset=" + offset + ", useStartLocation="
358                 + useStartLocation + ", reverse=" + reverse);
359     }
360 
361     /**
362      * Test property iterators, the core of the graph traversal engine
363      */
364     @Test
365     public void testIndividualIterators() {
366         testIndividual(+1, 0, true, false, 0);
367         testIndividual(-1, 0, true, false, 4);
368         testIndividual(0, -1, true, true, 4);
369         testIndividual(+1, -1, true, true, 4);
370         testIndividual(-1, -1, true, true, 0);
371         testIndividual(0, 1, true, false, 2);
372         testIndividual(0, 1, true, true, 1);
373         testIndividual(0, 0, false, false, 4);
374         testIndividual(0, 0, false, true, 4);
375     }
376 
377     @Test
378     public void testIterateAndSet() {
379         final JXPathContext context = JXPathContext.newContext(createContextBean());
380         Iterator<Pointer> it = context.iteratePointers("beans/int");
381         int i = 5;
382         while (it.hasNext()) {
383             final NodePointer pointer = (NodePointer) it.next();
384             pointer.setValue(Integer.valueOf(i++));
385         }
386         it = context.iteratePointers("beans/int");
387         final List<Object> actual = new ArrayList<>();
388         while (it.hasNext()) {
389             actual.add(it.next().getValue());
390         }
391         assertEquals(list(Integer.valueOf(5), Integer.valueOf(6)), actual, "Iterating <" + "beans/int" + ">");
392     }
393 
394     /**
395      * Test contributed by Kate Dvortsova
396      */
397     @Test
398     public void testIteratePointerSetValue() {
399         final JXPathContext context = JXPathContext.newContext(createContextBean());
400         assertXPathValue(context, "/beans[1]/name", "Name 1");
401         assertXPathValue(context, "/beans[2]/name", "Name 2");
402         // Test setting via context
403         context.setValue("/beans[2]/name", "Name 2 set");
404         assertXPathValue(context, "/beans[2]/name", "Name 2 set");
405         // Restore original value
406         context.setValue("/beans[2]/name", "Name 2");
407         assertXPathValue(context, "/beans[2]/name", "Name 2");
408         int iterCount = 0;
409         final Iterator<Pointer> iter = context.iteratePointers("/beans/name");
410         while (iter.hasNext()) {
411             iterCount++;
412             final Pointer pointer = iter.next();
413             String s = (String) pointer.getValue();
414             s += "suffix";
415             pointer.setValue(s);
416             assertEquals(s, pointer.getValue(), "pointer.getValue");
417             // fails right here, the value isn't getting set in the bean.
418             assertEquals(s, context.getValue(pointer.asPath()), "context.getValue");
419         }
420         assertEquals(2, iterCount, "Iteration count");
421         assertXPathValue(context, "/beans[1]/name", "Name 1suffix");
422         assertXPathValue(context, "/beans[2]/name", "Name 2suffix");
423     }
424 
425     @Test
426     public void testIteratePropertyArrayWithHasNext() {
427         final JXPathContext context = JXPathContext.newContext(createContextBean());
428         final Iterator<Pointer> it = context.iteratePointers("/integers");
429         final List<String> actual = new ArrayList<>();
430         while (it.hasNext()) {
431             actual.add(it.next().asPath());
432         }
433         assertEquals(list("/integers[1]", "/integers[2]", "/integers[3]", "/integers[4]"), actual, "Iterating 'hasNext'/'next'<" + "/integers" + ">");
434     }
435 
436     @Test
437     public void testIteratePropertyArrayWithoutHasNext() {
438         final JXPathContext context = JXPathContext.newContext(createContextBean());
439         final Iterator<Pointer> it = context.iteratePointers("/integers");
440         final List<String> actual = new ArrayList<>();
441         for (int i = 0; i < 4; i++) {
442             actual.add(it.next().toString());
443         }
444         assertEquals(list("/integers[1]", "/integers[2]", "/integers[3]", "/integers[4]"), actual, "Iterating 'next'<" + "/integers" + ">");
445     }
446 
447     private void testMultiple(final int propertyIndex, final int offset, final boolean useStartLocation, final boolean reverse, final int expected) {
448         final PropertyOwnerPointer root = (PropertyOwnerPointer) NodePointer.newNodePointer(new QName(null, "root"), createContextBean(), Locale.getDefault());
449         NodeIterator it;
450         PropertyPointer start = null;
451         if (useStartLocation) {
452             start = root.getPropertyPointer();
453             start.setPropertyIndex(propertyIndex);
454             start.setIndex(offset);
455         }
456         it = root.childIterator(null, reverse, start);
457         int size = 0;
458         while (it.setPosition(it.getPosition() + 1)) {
459 //            System.err.println("LOC: " + it.getCurrentNodePointer());
460             size++;
461         }
462         assertEquals(expected, size, "ITERATIONS: Multiple, propertyIndex=" + propertyIndex + ", offset=" + offset + ", useStartLocation=" + useStartLocation
463                 + ", reverse=" + reverse);
464     }
465 
466     /**
467      * Test property iterators with multiple properties returned
468      */
469     @Test
470     public void testMultipleIterators() {
471         testMultiple(0, 0, true, false, 20);
472         testMultiple(3, 0, true, false, 16);
473         testMultiple(3, -1, true, true, 8);
474         testMultiple(3, 0, true, true, 4);
475         testMultiple(0, 0, false, false, 21);
476         testMultiple(0, 0, false, true, 21);
477         testMultiple(3, 1, true, false, 15);
478         testMultiple(3, 3, true, false, 13);
479     }
480 
481     @Test
482     public void testRelativeContextAbsolutePath() {
483         final JXPathContext relative = context.getRelativeContext(context.getPointer("nestedBean"));
484         assertXPathValueAndPointer(relative, "/integers[2]", Integer.valueOf(2), "/integers[2]");
485     }
486 
487     @Test
488     public void testRelativeContextInheritance() {
489         context.setFunctions(new ClassFunctions(TestFunctions.class, "test"));
490         final JXPathContext relative = context.getRelativeContext(context.getPointer("nestedBean"));
491         assertXPathValue(relative, "test:countPointers(strings)", Integer.valueOf(3));
492     }
493 
494     @Test
495     public void testRelativeContextParent() {
496         final JXPathContext relative = context.getRelativeContext(context.getPointer("nestedBean"));
497         assertXPathValueAndPointer(relative, "../integers[2]", Integer.valueOf(2), "/integers[2]");
498     }
499 
500     @Test
501     public void testRelativeContextRelativePath() {
502         final JXPathContext relative = context.getRelativeContext(context.getPointer("nestedBean"));
503         assertXPathValueAndPointer(relative, "int", Integer.valueOf(1), "/nestedBean/int");
504     }
505 
506     @Test
507     public void testRemoveAllArrayElements() {
508         context.removeAll("nestedBean/strings");
509         assertXPathValueIterator(context, "nestedBean/strings", list());
510     }
511 
512     @Test
513     public void testRemoveAllListElements() {
514         context.removeAll("list");
515         assertXPathValueIterator(context, "list", this instanceof DynaBeanModelTest ? list(null, null, null) : list());
516     }
517 
518     @Test
519     public void testRemoveAllMapEntries() {
520         context.removeAll("map/*");
521         assertXPathValue(context, "map", Collections.EMPTY_MAP);
522     }
523 
524     @Test
525     public void testRemovePathArrayElement() {
526         // Assigns a new array to the property
527         context.removePath("nestedBean/strings[1]");
528         assertEquals("String 2", context.getValue("nestedBean/strings[1]"), "Remove array element");
529     }
530 
531     @Test
532     public void testRemovePathBeanValue() {
533         context.removePath("nestedBean");
534         assertNull(context.getValue("nestedBean"), "Remove collection element");
535     }
536 
537     @Test
538     public void testRemovePathPropertyValue() {
539         // Remove property value
540         context.removePath("nestedBean/int");
541         assertEquals(Integer.valueOf(0), context.getValue("nestedBean/int"), "Remove property value");
542     }
543 
544     @Test
545     public void testRoot() {
546         assertXPathValueAndPointer(context, "/", context.getContextBean(), "/");
547     }
548 
549     @Test
550     public void testSetCollectionElement() {
551         // Collection element
552         assertXPathSetValue(context, "integers[2]", Integer.valueOf(5));
553         // Collection element with conversion
554         assertXPathSetValue(context, "integers[2]", new int[] { 6 }, Integer.valueOf(6));
555     }
556 
557     @Test
558     public void testSetContextDependentNode() {
559         // Find node without using SimplePathInterpreter
560         assertXPathSetValue(context, "integers[position() = 1]", Integer.valueOf(8));
561         // Find node without using SimplePathInterpreter and set its property
562         assertXPathSetValue(context, "beans[name = 'Name 1']/int", Integer.valueOf(9));
563     }
564 
565     @Test
566     public void testSetNonPrimitiveValue() {
567         // First, let's see if we can set a collection element to null
568         assertXPathSetValue(context, "beans[2]", null);
569         // Now, assign it a whole bean
570         context.setValue("beans[2]", new NestedTestBean("Name 9"));
571         assertEquals("Name 9", context.getValue("beans[2]/name"), "Modified <" + "beans[2]/name" + ">");
572     }
573 
574     @Test
575     public void testSetPropertyValue() {
576         // Simple property
577         assertXPathSetValue(context, "int", Integer.valueOf(2));
578         // Simple property with conversion from string
579         assertXPathSetValue(context, "int", "3", Integer.valueOf(3));
580         // Simple property with conversion from array
581         assertXPathSetValue(context, "int", new int[] { 4 }, Integer.valueOf(4));
582         // Attribute (which is the same as a child for beans
583         assertXPathSetValue(context, "@int", Integer.valueOf(10));
584     }
585 
586     @Test
587     public void testUnion() {
588         // Union - note corrected document order
589         assertXPathValueIterator(context, "integers | beans[1]/strings",
590                 list("String 1", "String 2", "String 3", Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4)));
591         assertXPathValue(context, "count((integers | beans[1]/strings)[contains(., '1')])", Double.valueOf(2));
592         assertXPathValue(context, "count((integers | beans[1]/strings)[name(.) = 'strings'])", Double.valueOf(3));
593         // Note that the following is different from "integer[2]" -
594         // it is a filter expression
595         assertXPathValue(context, "(integers)[2]", Integer.valueOf(2));
596     }
597 }