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.axes;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  
24  import java.util.HashMap;
25  
26  import org.apache.commons.jxpath.JXPathContext;
27  import org.apache.commons.jxpath.NestedTestBean;
28  import org.apache.commons.jxpath.Pointer;
29  import org.apache.commons.jxpath.TestNull;
30  import org.apache.commons.jxpath.ri.model.NodePointer;
31  import org.apache.commons.jxpath.ri.model.VariablePointer;
32  import org.apache.commons.jxpath.ri.model.beans.BeanPointer;
33  import org.apache.commons.jxpath.ri.model.beans.BeanPropertyPointer;
34  import org.apache.commons.jxpath.ri.model.beans.CollectionPointer;
35  import org.apache.commons.jxpath.ri.model.beans.NullElementPointer;
36  import org.apache.commons.jxpath.ri.model.beans.NullPointer;
37  import org.apache.commons.jxpath.ri.model.beans.NullPropertyPointer;
38  import org.apache.commons.jxpath.ri.model.beans.TestBeanFactory;
39  import org.apache.commons.jxpath.ri.model.dom.DOMNodePointer;
40  import org.apache.commons.jxpath.ri.model.dynamic.DynamicPointer;
41  import org.apache.commons.jxpath.ri.model.dynamic.DynamicPropertyPointer;
42  import org.junit.jupiter.api.BeforeEach;
43  import org.junit.jupiter.api.Test;
44  
45  public class SimplePathInterpreterTest {
46  
47      private TestBeanWithNode bean;
48      private JXPathContext context;
49  
50      private void assertNullPointer(final String path, final String expectedPath, final String expectedSignature) {
51          final Pointer pointer = context.getPointer(path);
52          assertNotNull(pointer, "Null path exists: " + path);
53          assertEquals(expectedPath, pointer.asPath(), "Null path as path: " + path);
54          assertEquals(expectedSignature, pointerSignature(pointer), "Checking Signature: " + path);
55          final Pointer vPointer = ((NodePointer) pointer).getValuePointer();
56          assertFalse(((NodePointer) vPointer).isActual(), "Null path is null: " + path);
57          assertEquals(expectedSignature + "N", pointerSignature(vPointer), "Checking value pointer signature: " + path);
58      }
59  
60      private void assertValueAndPointer(final String path, final Object expectedValue, final String expectedPath, final String expectedSignature) {
61          assertValueAndPointer(path, expectedValue, expectedPath, expectedSignature, expectedSignature);
62      }
63  
64      private void assertValueAndPointer(final String path, final Object expectedValue, final String expectedPath, final String expectedSignature,
65              final String expectedValueSignature) {
66          final Object value = context.getValue(path);
67          assertEquals(expectedValue, value, "Checking value: " + path);
68          final Pointer pointer = context.getPointer(path);
69          assertEquals(expectedPath, pointer.toString(), "Checking pointer: " + path);
70          assertEquals(expectedSignature, pointerSignature(pointer), "Checking signature: " + path);
71          final Pointer vPointer = ((NodePointer) pointer).getValuePointer();
72          assertEquals(expectedValueSignature, pointerSignature(vPointer), "Checking value pointer signature: " + path);
73      }
74  
75      /**
76       * Since we need to test the internal Signature of a pointer, we will get a signature which will contain a single character per pointer in the chain,
77       * representing that pointer's type.
78       */
79      private String pointerSignature(final Pointer pointer) {
80          if (pointer == null) {
81              return "";
82          }
83          char type = '?';
84          if (pointer instanceof NullPointer) {
85              type = 'N';
86          } else if (pointer instanceof NullPropertyPointer) {
87              type = 'n';
88          } else if (pointer instanceof NullElementPointer) {
89              type = 'E';
90          } else if (pointer instanceof VariablePointer) {
91              type = 'V';
92          } else if (pointer instanceof CollectionPointer) {
93              type = 'C';
94          } else if (pointer instanceof BeanPointer) {
95              type = 'B';
96          } else if (pointer instanceof BeanPropertyPointer) {
97              type = 'b';
98          } else if (pointer instanceof DynamicPointer) {
99              type = 'D';
100         } else if (pointer instanceof DynamicPropertyPointer) {
101             type = 'd';
102         } else if (pointer instanceof DOMNodePointer) {
103             type = 'M';
104         } else {
105             System.err.println("UNKNOWN TYPE: " + pointer.getClass());
106         }
107         final NodePointer parent = ((NodePointer) pointer).getImmediateParentPointer();
108         return pointerSignature(parent) + type;
109     }
110 
111     @BeforeEach
112     protected void setUp() throws Exception {
113         bean = TestBeanWithNode.createTestBeanWithDOM();
114         final HashMap submap = new HashMap();
115         submap.put("key", new NestedTestBean("Name 9"));
116         submap.put("strings", bean.getNestedBean().getStrings());
117         bean.getList().add(new int[] { 1, 2 });
118         bean.getList().add(bean.getVendor());
119         bean.getMap().put("Key3", new Object[] { new NestedTestBean("some"), Integer.valueOf(2), bean.getVendor(), submap });
120         bean.getMap().put("Key4", bean.getVendor());
121         bean.getMap().put("Key5", submap);
122         bean.getMap().put("Key6", new Object[0]);
123         context = JXPathContext.newContext(null, bean);
124         context.setLenient(true);
125         context.setFactory(new TestBeanFactory());
126     }
127 
128     @Test
129     public void testDoPredicateIndex() {
130         // Existing dynamic property + existing property + index
131         assertValueAndPointer("/map[@name='Key2'][@name='strings'][2]", "String 2", "/map[@name='Key2']/strings[2]", "BbDdBb", "BbDdBbB");
132         // existingProperty[@name=collectionProperty][index]
133         assertValueAndPointer("/nestedBean[@name='strings'][2]", bean.getNestedBean().getStrings()[1], "/nestedBean/strings[2]", "BbBb", "BbBbB");
134         // existingProperty[@name=missingProperty][index]
135         assertNullPointer("/nestedBean[@name='foo'][3]", "/nestedBean[@name='foo'][3]", "BbBn");
136         // existingProperty[@name=collectionProperty][missingIndex]
137         assertNullPointer("/nestedBean[@name='strings'][5]", "/nestedBean/strings[5]", "BbBbE");
138         // map[@name=collectionProperty][index]
139         assertValueAndPointer("/map[@name='Key3'][2]", Integer.valueOf(2), "/map[@name='Key3'][2]", "BbDd", "BbDdB");
140         // map[@name=collectionProperty][missingIndex]
141         assertNullPointer("/map[@name='Key3'][5]", "/map[@name='Key3'][5]", "BbDdE");
142         // map[@name=collectionProperty][missingIndex]/property
143         assertNullPointer("/map[@name='Key3'][5]/foo", "/map[@name='Key3'][5]/foo", "BbDdENn");
144         // map[@name=map][@name=collection][index]
145         assertValueAndPointer("/map[@name='Key5'][@name='strings'][2]", "String 2", "/map[@name='Key5'][@name='strings'][2]", "BbDdDd", "BbDdDdB");
146         // map[@name=map][@name=collection][missingIndex]
147         assertNullPointer("/map[@name='Key5'][@name='strings'][5]", "/map[@name='Key5'][@name='strings'][5]", "BbDdDdE");
148         // Existing dynamic property + indexing
149         assertValueAndPointer("/map[@name='Key3'][2]", Integer.valueOf(2), "/map[@name='Key3'][2]", "BbDd", "BbDdB");
150         // Existing dynamic property + indexing
151         assertValueAndPointer("/map[@name='Key3'][1]/name", "some", "/map[@name='Key3'][1]/name", "BbDdBb", "BbDdBbB");
152         // map[@name=missingProperty][index]
153         assertNullPointer("/map[@name='foo'][3]", "/map[@name='foo'][3]", "BbDdE");
154         // collectionProperty[index]
155         assertValueAndPointer("/integers[2]", Integer.valueOf(2), "/integers[2]", "Bb", "BbB");
156         // existingProperty/collectionProperty[index]
157         assertValueAndPointer("/nestedBean/strings[2]", bean.getNestedBean().getStrings()[1], "/nestedBean/strings[2]", "BbBb", "BbBbB");
158         // existingProperty[index]/existingProperty
159         assertValueAndPointer("/list[3]/int", Integer.valueOf(1), "/list[3]/int", "BbBb", "BbBbB");
160         // existingProperty[missingIndex]
161         assertNullPointer("/list[6]", "/list[6]", "BbE");
162         // existingProperty/missingProperty[index]
163         assertNullPointer("/nestedBean/foo[3]", "/nestedBean/foo[3]", "BbBn");
164         // map[@name=missingProperty][index]
165         assertNullPointer("/map/foo[3]", "/map[@name='foo'][3]", "BbDdE");
166         // existingProperty/collectionProperty[missingIndex]
167         assertNullPointer("/nestedBean/strings[5]", "/nestedBean/strings[5]", "BbBbE");
168         // map/collectionProperty[missingIndex]/property
169         assertNullPointer("/map/Key3[5]/foo", "/map[@name='Key3'][5]/foo", "BbDdENn");
170         // map[@name=map]/collection[index]
171         assertValueAndPointer("/map[@name='Key5']/strings[2]", "String 2", "/map[@name='Key5'][@name='strings'][2]", "BbDdDd", "BbDdDdB");
172         // map[@name=map]/collection[missingIndex]
173         assertNullPointer("/map[@name='Key5']/strings[5]", "/map[@name='Key5'][@name='strings'][5]", "BbDdDdE");
174         // scalarPropertyAsCollection[index]
175         assertValueAndPointer("/int[1]", Integer.valueOf(1), "/int", "Bb", "BbB");
176         // scalarPropertyAsCollection[index]
177         assertValueAndPointer(".[1]/int", Integer.valueOf(1), "/int", "Bb", "BbB");
178     }
179 
180     @Test
181     public void testDoPredicateName() {
182         // existingProperty[@name=existingProperty]
183         assertValueAndPointer("/nestedBean[@name='int']", Integer.valueOf(1), "/nestedBean/int", "BbBb", "BbBbB");
184         // /self::node()[@name=existingProperty]
185         assertValueAndPointer("/.[@name='int']", Integer.valueOf(1), "/int", "Bb", "BbB");
186         // dynamicProperty[@name=existingProperty]
187         assertValueAndPointer("/map[@name='Key1']", "Value 1", "/map[@name='Key1']", "BbDd", "BbDdB");
188         // existingProperty[@name=collectionProperty]
189         assertValueAndPointer("/nestedBean[@name='strings']", bean.getNestedBean().getStrings(), "/nestedBean/strings", "BbBb", "BbBbC");
190         // existingProperty[@name=missingProperty]
191         assertNullPointer("/nestedBean[@name='foo']", "/nestedBean[@name='foo']", "BbBn");
192         // map[@name=collectionProperty]
193         assertValueAndPointer("/map[@name='Key3']", bean.getMap().get("Key3"), "/map[@name='Key3']", "BbDd", "BbDdC");
194         // map[@name=missingProperty]
195         assertNullPointer("/map[@name='foo']", "/map[@name='foo']", "BbDd");
196         // collectionProperty[@name=...] (find node)
197         assertValueAndPointer("/list[@name='fruitco']", context.getValue("/vendor"), "/list[5]", "BbCM");
198         // collectionProperty[@name=...] (find map entry)
199         assertValueAndPointer("/map/Key3[@name='key']/name", "Name 9", "/map[@name='Key3'][4][@name='key']/name", "BbDdCDdBb", "BbDdCDdBbB");
200         // map/collectionProperty[@name...]
201         assertValueAndPointer("map/Key3[@name='fruitco']", context.getValue("/vendor"), "/map[@name='Key3'][3]", "BbDdCM");
202         // Bean property -> DOM Node, name match
203         assertValueAndPointer("/vendor[@name='fruitco']", context.getValue("/vendor"), "/vendor", "BbM");
204         // Bean property -> DOM Node, name mismatch
205         assertNullPointer("/vendor[@name='foo']", "/vendor[@name='foo']", "BbMn");
206         assertNullPointer("/vendor[@name='foo'][3]", "/vendor[@name='foo'][3]", "BbMn");
207         // existingProperty(bean)[@name=missingProperty]/anotherStep
208         assertNullPointer("/nestedBean[@name='foo']/bar", "/nestedBean[@name='foo']/bar", "BbBnNn");
209         // map[@name=missingProperty]/anotherStep
210         assertNullPointer("/map[@name='foo']/bar", "/map[@name='foo']/bar", "BbDdNn");
211         // existingProperty(node)[@name=missingProperty]/anotherStep
212         assertNullPointer("/vendor[@name='foo']/bar", "/vendor[@name='foo']/bar", "BbMnNn");
213         // existingProperty(node)[@name=missingProperty][index]/anotherStep
214         assertNullPointer("/vendor[@name='foo'][3]/bar", "/vendor[@name='foo'][3]/bar", "BbMnNn");
215         // Existing dynamic property + existing property
216         assertValueAndPointer("/map[@name='Key2'][@name='name']", "Name 6", "/map[@name='Key2']/name", "BbDdBb", "BbDdBbB");
217         // Existing dynamic property + existing property + index
218         assertValueAndPointer("/map[@name='Key2'][@name='strings'][2]", "String 2", "/map[@name='Key2']/strings[2]", "BbDdBb", "BbDdBbB");
219         // bean/map/map/property
220         assertValueAndPointer("map[@name='Key5'][@name='key']/name", "Name 9", "/map[@name='Key5'][@name='key']/name", "BbDdDdBb", "BbDdDdBbB");
221         assertNullPointer("map[@name='Key2'][@name='foo']", "/map[@name='Key2'][@name='foo']", "BbDdBn");
222         assertNullPointer("map[@name='Key2'][@name='foo'][@name='bar']", "/map[@name='Key2'][@name='foo'][@name='bar']", "BbDdBnNn");
223         // bean/map/node
224         assertValueAndPointer("map[@name='Key4'][@name='fruitco']", context.getValue("/vendor"), "/map[@name='Key4']", "BbDdM");
225     }
226 
227     @Test
228     public void testDoPredicatesStandard() {
229         // bean/map/collection/node
230         assertValueAndPointer("map[@name='Key3'][@name='fruitco']", context.getValue("/vendor"), "/map[@name='Key3'][3]", "BbDdCM");
231         // bean/map/collection/missingNode
232         assertNullPointer("map[@name='Key3'][@name='foo']", "/map[@name='Key3'][4][@name='foo']", "BbDdCDd");
233         // bean/map/node
234         assertValueAndPointer("map[@name='Key4'][@name='fruitco']", context.getValue("/vendor"), "/map[@name='Key4']", "BbDdM");
235         // bean/map/emptyCollection[@name=foo]
236         assertNullPointer("map[@name='Key6'][@name='fruitco']", "/map[@name='Key6'][@name='fruitco']", "BbDdCn");
237         // bean/node[@name=foo][index]
238         assertValueAndPointer("/vendor/contact[@name='jack'][2]", "Jack Black", "/vendor/contact[4]", "BbMM");
239         // bean/node[@name=foo][missingIndex]
240         assertNullPointer("/vendor/contact[@name='jack'][5]", "/vendor/contact[@name='jack'][5]", "BbMnNn");
241         // bean/node/.[@name=foo][index]
242         assertValueAndPointer("/vendor/contact/.[@name='jack']", "Jack", "/vendor/contact[2]", "BbMM");
243     }
244 
245     @Test
246     public void testDoStepNoPredicatesPropertyOwner() {
247         // Existing scalar property
248         assertValueAndPointer("/int", Integer.valueOf(1), "/int", "Bb", "BbB");
249         // self::
250         assertValueAndPointer("/./int", Integer.valueOf(1), "/int", "Bb", "BbB");
251         // Missing property
252         assertNullPointer("/foo", "/foo", "Bn");
253         // existingProperty/existingScalarProperty
254         assertValueAndPointer("/nestedBean/int", Integer.valueOf(1), "/nestedBean/int", "BbBb", "BbBbB");
255         // existingProperty/collectionProperty
256         assertValueAndPointer("/nestedBean/strings", bean.getNestedBean().getStrings(), "/nestedBean/strings", "BbBb", "BbBbC");
257         // existingProperty/missingProperty
258         assertNullPointer("/nestedBean/foo", "/nestedBean/foo", "BbBn");
259         // map/missingProperty
260         assertNullPointer("/map/foo", "/map[@name='foo']", "BbDd");
261         // Existing property by search in collection
262         assertValueAndPointer("/list/int", Integer.valueOf(1), "/list[3]/int", "BbBb", "BbBbB");
263         // Missing property by search in collection
264         assertNullPointer("/list/foo", "/list[1]/foo", "BbBn");
265         // existingProperty/missingProperty/missingProperty
266         assertNullPointer("/nestedBean/foo/bar", "/nestedBean/foo/bar", "BbBnNn");
267         // collection/existingProperty/missingProperty
268         assertNullPointer("/list/int/bar", "/list[3]/int/bar", "BbBbBn");
269         // collectionProperty/missingProperty/missingProperty
270         assertNullPointer("/list/foo/bar", "/list[1]/foo/bar", "BbBnNn");
271         // map/missingProperty/anotherStep
272         assertNullPointer("/map/foo/bar", "/map[@name='foo']/bar", "BbDdNn");
273         // Existing dynamic property
274         assertValueAndPointer("/map/Key1", "Value 1", "/map[@name='Key1']", "BbDd", "BbDdB");
275         // collectionProperty
276         assertValueAndPointer("/integers", bean.getIntegers(), "/integers", "Bb", "BbC");
277     }
278 
279     @Test
280     public void testDoStepNoPredicatesStandard() {
281         // Existing DOM node
282         assertValueAndPointer("/vendor/location/address/city", "Fruit Market", "/vendor/location[2]/address[1]/city[1]", "BbMMMM");
283         // Missing DOM node
284         assertNullPointer("/vendor/location/address/pity", "/vendor/location[1]/address[1]/pity", "BbMMMn");
285         // Missing DOM node inside a missing element
286         assertNullPointer("/vendor/location/address/itty/bitty", "/vendor/location[1]/address[1]/itty/bitty", "BbMMMnNn");
287         // Missing DOM node by search for the best match
288         assertNullPointer("/vendor/location/address/city/pretty", "/vendor/location[2]/address[1]/city[1]/pretty", "BbMMMMn");
289     }
290 
291     @Test
292     public void testDoStepPredicatesPropertyOwner() {
293         // missingProperty[@name=foo]
294         assertNullPointer("/foo[@name='foo']", "/foo[@name='foo']", "BnNn");
295         // missingProperty[index]
296         assertNullPointer("/foo[3]", "/foo[3]", "Bn");
297     }
298 
299     @Test
300     public void testDoStepPredicatesStandard() {
301         // Looking for an actual XML attribute called "name"
302         // nodeProperty/name[@name=value]
303         assertValueAndPointer("/vendor/contact[@name='jack']", "Jack", "/vendor/contact[2]", "BbMM");
304         // Indexing in XML
305         assertValueAndPointer("/vendor/contact[2]", "Jack", "/vendor/contact[2]", "BbMM");
306         // Indexing in XML, no result
307         assertNullPointer("/vendor/contact[5]", "/vendor/contact[5]", "BbMn");
308         // Combination of search by name and indexing in XML
309         assertValueAndPointer("/vendor/contact[@name='jack'][2]", "Jack Black", "/vendor/contact[4]", "BbMM");
310         // Combination of search by name and indexing in XML
311         assertValueAndPointer("/vendor/contact[@name='jack'][2]", "Jack Black", "/vendor/contact[4]", "BbMM");
312     }
313 
314     @Test
315     public void testInterpretExpressionPath() {
316         context.getVariables().declareVariable("array", new String[] { "Value1" });
317         context.getVariables().declareVariable("testnull", new TestNull());
318         assertNullPointer("$testnull/nothing[2]", "$testnull/nothing[2]", "VBbE");
319     }
320 }