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