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  package org.apache.commons.jxpath.ri.compiler;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.List;
22  import java.util.Locale;
23  
24  import org.apache.commons.jxpath.ClassFunctions;
25  import org.apache.commons.jxpath.ExpressionContext;
26  import org.apache.commons.jxpath.Function;
27  import org.apache.commons.jxpath.FunctionLibrary;
28  import org.apache.commons.jxpath.Functions;
29  import org.apache.commons.jxpath.JXPathContext;
30  import org.apache.commons.jxpath.JXPathTestCase;
31  import org.apache.commons.jxpath.NodeSet;
32  import org.apache.commons.jxpath.PackageFunctions;
33  import org.apache.commons.jxpath.Pointer;
34  import org.apache.commons.jxpath.TestBean;
35  import org.apache.commons.jxpath.Variables;
36  import org.apache.commons.jxpath.ri.model.NodePointer;
37  import org.apache.commons.jxpath.util.JXPath11CompatibleTypeConverter;
38  import org.apache.commons.jxpath.util.TypeConverter;
39  import org.apache.commons.jxpath.util.TypeUtils;
40  
41  /**
42   * Test extension functions.
43   *
44   * @author Dmitri Plotnikov
45   * @version $Revision: 652845 $ $Date: 2008-05-02 19:46:46 +0200 (Fr, 02 Mai 2008) $
46   */
47  public class ExtensionFunctionTest extends JXPathTestCase {
48      private Functions functions;
49      private JXPathContext context;
50      private TestBean testBean;
51      private TypeConverter typeConverter;
52  
53      public void setUp() {
54          if (context == null) {
55              testBean = new TestBean();
56              context = JXPathContext.newContext(testBean);
57              Variables vars = context.getVariables();
58              vars.declareVariable("test", new TestFunctions(4, "test"));
59  
60              FunctionLibrary lib = new FunctionLibrary();
61              lib.addFunctions(new ClassFunctions(TestFunctions.class, "test"));
62              lib.addFunctions(new ClassFunctions(TestFunctions2.class, "test"));
63              lib.addFunctions(new PackageFunctions("", "call"));
64              lib.addFunctions(
65                  new PackageFunctions(
66                      "org.apache.commons.jxpath.ri.compiler.",
67                      "jxpathtest"));
68              lib.addFunctions(new PackageFunctions("", null));
69              context.setFunctions(lib);
70              context.getVariables().declareVariable("List.class", List.class);
71              context.getVariables().declareVariable("NodeSet.class", NodeSet.class);
72          }
73          functions = new ClassFunctions(TestFunctions.class, "test");
74          typeConverter = TypeUtils.getTypeConverter();
75      }
76  
77      public void tearDown() {
78          TypeUtils.setTypeConverter(typeConverter);
79      }
80  
81      public void testConstructorLookup() {
82          Object[] args = new Object[] { new Integer(1), "x" };
83          Function func = functions.getFunction("test", "new", args);
84  
85          assertEquals(
86              "test:new(1, x)",
87              func.invoke(new Context(null), args).toString(),
88              "foo=1; bar=x");
89      }
90  
91      public void testConstructorLookupWithExpressionContext() {
92          Object[] args = new Object[] { "baz" };
93          Function func = functions.getFunction("test", "new", args);
94          assertEquals(
95              "test:new('baz')",
96              func.invoke(new Context(new Integer(1)), args).toString(),
97              "foo=1; bar=baz");
98      }
99  
100     public void testStaticMethodLookup() {
101         Object[] args = new Object[] { new Integer(1), "x" };
102         Function func = functions.getFunction("test", "build", args);
103         assertEquals(
104             "test:build(1, x)",
105             func.invoke(new Context(null), args).toString(),
106             "foo=1; bar=x");
107     }
108 
109     public void testStaticMethodLookupWithConversion() {
110         Object[] args = new Object[] { "7", new Integer(1)};
111         Function func = functions.getFunction("test", "build", args);
112         assertEquals(
113             "test:build('7', 1)",
114             func.invoke(new Context(null), args).toString(),
115             "foo=7; bar=1");
116     }
117 
118     public void testMethodLookup() {
119         Object[] args = new Object[] { new TestFunctions()};
120         Function func = functions.getFunction("test", "getFoo", args);
121         assertEquals(
122             "test:getFoo($test, 1, x)",
123             func.invoke(new Context(null), args).toString(),
124             "0");
125     }
126 
127     public void testStaticMethodLookupWithExpressionContext() {
128         Object[] args = new Object[0];
129         Function func = functions.getFunction("test", "path", args);
130         assertEquals(
131             "test:path()",
132             func.invoke(new Context(new Integer(1)), args),
133             "1");
134     }
135 
136     public void testMethodLookupWithExpressionContext() {
137         Object[] args = new Object[] { new TestFunctions()};
138         Function func = functions.getFunction("test", "instancePath", args);
139         assertEquals(
140             "test:instancePath()",
141             func.invoke(new Context(new Integer(1)), args),
142             "1");
143     }
144 
145     public void testMethodLookupWithExpressionContextAndArgument() {
146         Object[] args = new Object[] { new TestFunctions(), "*" };
147         Function func = functions.getFunction("test", "pathWithSuffix", args);
148         assertEquals(
149             "test:pathWithSuffix('*')",
150             func.invoke(new Context(new Integer(1)), args),
151             "1*");
152     }
153 
154     public void testAllocation() {
155         
156         // Allocate new object using the default constructor
157         assertXPathValue(context, "string(test:new())", "foo=0; bar=null");
158 
159         // Allocate new object using PackageFunctions and class name
160         assertXPathValue(
161             context,
162             "string(jxpathtest:TestFunctions.new())",
163             "foo=0; bar=null");
164 
165         // Allocate new object using a fully qualified class name
166         assertXPathValue(
167             context,
168             "string(" + TestFunctions.class.getName() + ".new())",
169             "foo=0; bar=null");
170 
171         // Allocate new object using a custom constructor
172         assertXPathValue(
173             context,
174             "string(test:new(3, 'baz'))",
175             "foo=3; bar=baz");
176 
177         // Allocate new object using a custom constructor - type conversion
178         assertXPathValue(context, "string(test:new('3', 4))", "foo=3; bar=4.0");
179         
180         context.getVariables().declareVariable("A", "baz");        
181         assertXPathValue(
182                 context,
183                 "string(test:new(2, $A, false))",
184                 "foo=2; bar=baz");
185     }
186 
187     public void testMethodCall() {
188         assertXPathValue(context, "length('foo')", new Integer(3));
189 
190         // We are just calling a method - prefix is ignored
191         assertXPathValue(context, "call:substring('foo', 1, 2)", "o");
192 
193         // Invoke a function implemented as a regular method
194         assertXPathValue(context, "string(test:getFoo($test))", "4");
195         
196         // Note that the prefix is ignored anyway, we are just calling a method
197         assertXPathValue(context, "string(call:getFoo($test))", "4");
198 
199         // We don't really need to supply a prefix in this case
200         assertXPathValue(context, "string(getFoo($test))", "4");
201 
202         // Method with two arguments
203         assertXPathValue(
204             context,
205             "string(test:setFooAndBar($test, 7, 'biz'))",
206             "foo=7; bar=biz");
207     }
208     
209     public void testCollectionMethodCall() {
210         
211         List list = new ArrayList();
212         list.add("foo");
213         context.getVariables().declareVariable("myList", list);
214 
215         assertXPathValue(
216             context, 
217             "size($myList)", 
218             new Integer(1));
219     
220         assertXPathValue(
221             context, 
222             "size(beans)", 
223             new Integer(2));
224             
225         context.getValue("add($myList, 'hello')");
226         assertEquals("After adding an element", 2, list.size());
227         
228         JXPathContext context = JXPathContext.newContext(new ArrayList());
229         assertEquals("Extension function on root collection", "0", String
230                 .valueOf(context.getValue("size(/)")));
231     }
232 
233     public void testStaticMethodCall() {
234 
235         assertXPathValue(
236             context,
237             "string(test:build(8, 'goober'))",
238             "foo=8; bar=goober");
239 
240         // Call a static method using PackageFunctions and class name
241         assertXPathValue(
242             context,
243             "string(jxpathtest:TestFunctions.build(8, 'goober'))",
244             "foo=8; bar=goober");
245 
246         // Call a static method with a fully qualified class name
247         assertXPathValue(
248             context,
249             "string(" + TestFunctions.class.getName() + ".build(8, 'goober'))",
250             "foo=8; bar=goober");
251 
252         // Two ClassFunctions are sharing the same prefix.
253         // This is TestFunctions2
254         assertXPathValue(context, "string(test:increment(8))", "9");
255         
256         // See that a NodeSet gets properly converted to a string
257         assertXPathValue(context, "test:string(/beans/name)", "Name 1");
258     }
259 
260     public void testExpressionContext() {
261         // Execute an extension function for each node while searching
262         // The function uses ExpressionContext to get to the current
263         // node.
264         assertXPathValue(
265             context, 
266             "//.[test:isMap()]/Key1", 
267             "Value 1");
268 
269         // The function gets all
270         // nodes in the context that match the pattern.
271         assertXPathValue(
272             context,
273             "count(//.[test:count(strings) = 3])",
274             new Double(7));
275 
276         // The function receives a collection of strings
277         // and checks their type for testing purposes            
278         assertXPathValue(
279             context,
280             "test:count(//strings)",
281             new Integer(21));
282 
283         
284         // The function receives a collection of pointers
285         // and checks their type for testing purposes            
286         assertXPathValue(
287             context,
288             "test:countPointers(//strings)",
289             new Integer(21));
290             
291         // The function uses ExpressionContext to get to the current
292         // pointer and returns its path.
293         assertXPathValue(
294             context,
295             "/beans[contains(test:path(), '[2]')]/name",
296             "Name 2");
297     }
298     
299     public void testCollectionReturn() {
300         assertXPathValueIterator(
301             context,
302             "test:collection()/name",
303             list("foo", "bar"));
304 
305         assertXPathPointerIterator(
306             context,
307             "test:collection()/name",
308             list("/.[1]/name", "/.[2]/name"));
309             
310         assertXPathValue(
311             context,
312             "test:collection()/name",
313             "foo");        
314 
315         assertXPathValue(
316             context,
317             "test:collection()/@name",
318             "foo");   
319         
320         List list = new ArrayList();
321         list.add("foo");
322         list.add("bar");
323         context.getVariables().declareVariable("list", list);
324         Object values = context.getValue("test:items($list)");
325         assertTrue("Return type: ", values instanceof Collection);
326         assertEquals(
327             "Return values: ",
328             list,
329             new ArrayList((Collection) values));
330     }
331 
332     public void testNodeSetReturn() {
333         assertXPathValueIterator(
334             context,
335             "test:nodeSet()/name",
336             list("Name 1", "Name 2"));
337 
338         assertXPathValueIterator(
339             context,
340             "test:nodeSet()",
341             list(testBean.getBeans()[0], testBean.getBeans()[1]));
342 
343         assertXPathPointerIterator(
344             context,
345             "test:nodeSet()/name",
346             list("/beans[1]/name", "/beans[2]/name"));
347             
348         assertXPathValueAndPointer(
349             context,
350             "test:nodeSet()/name",
351             "Name 1",
352             "/beans[1]/name");        
353 
354         assertXPathValueAndPointer(
355             context,
356             "test:nodeSet()/@name",
357             "Name 1",
358             "/beans[1]/@name");
359 
360         assertEquals(2, ((Number) context.getValue("count(test:nodeSet())")).intValue());
361 
362         assertXPathValue(context, "test:nodeSet()", testBean.getBeans()[0]);
363     }
364 
365     public void testEstablishNodeSetBaseline() {
366         assertXPathValue(
367             context,
368             "test:isInstance(//strings, $List.class)",
369             Boolean.TRUE);
370         assertXPathValue(
371             context,
372             "test:isInstance(//strings, $NodeSet.class)",
373             Boolean.FALSE);
374     }
375 
376     public void testBCNodeSetHack() {
377         TypeUtils.setTypeConverter(new JXPath11CompatibleTypeConverter());
378         assertXPathValue(
379             context,
380             "test:isInstance(//strings, $List.class)",
381             Boolean.FALSE);
382         assertXPathValue(
383             context,
384             "test:isInstance(//strings, $NodeSet.class)",
385             Boolean.TRUE);
386     }
387 
388     private static class Context implements ExpressionContext {
389         private Object object;
390 
391         public Context(Object object) {
392             this.object = object;
393         }
394 
395         public Pointer getContextNodePointer() {
396             return NodePointer
397                     .newNodePointer(null, object, Locale.getDefault());
398         }
399 
400         public List getContextNodeList() {
401             return null;
402         }
403 
404         public JXPathContext getJXPathContext() {
405             return null;
406         }
407 
408         public int getPosition() {
409             return 0;
410         }
411     }
412 }