/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.jxpath.ri.compiler; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; import org.apache.commons.jxpath.ClassFunctions; import org.apache.commons.jxpath.ExpressionContext; import org.apache.commons.jxpath.Function; import org.apache.commons.jxpath.FunctionLibrary; import org.apache.commons.jxpath.Functions; import org.apache.commons.jxpath.JXPathContext; import org.apache.commons.jxpath.JXPathTestCase; import org.apache.commons.jxpath.NodeSet; import org.apache.commons.jxpath.PackageFunctions; import org.apache.commons.jxpath.Pointer; import org.apache.commons.jxpath.TestBean; import org.apache.commons.jxpath.Variables; import org.apache.commons.jxpath.ri.model.NodePointer; import org.apache.commons.jxpath.util.JXPath11CompatibleTypeConverter; import org.apache.commons.jxpath.util.TypeConverter; import org.apache.commons.jxpath.util.TypeUtils; /** * Test extension functions. * * @author Dmitri Plotnikov * @version $Revision: 652845 $ $Date: 2008-05-02 12:46:46 -0500 (Fri, 02 May 2008) $ */ public class ExtensionFunctionTest extends JXPathTestCase { private Functions functions; private JXPathContext context; private TestBean testBean; private TypeConverter typeConverter; public void setUp() { if (context == null) { testBean = new TestBean(); context = JXPathContext.newContext(testBean); Variables vars = context.getVariables(); vars.declareVariable("test", new TestFunctions(4, "test")); FunctionLibrary lib = new FunctionLibrary(); lib.addFunctions(new ClassFunctions(TestFunctions.class, "test")); lib.addFunctions(new ClassFunctions(TestFunctions2.class, "test")); lib.addFunctions(new PackageFunctions("", "call")); lib.addFunctions( new PackageFunctions( "org.apache.commons.jxpath.ri.compiler.", "jxpathtest")); lib.addFunctions(new PackageFunctions("", null)); context.setFunctions(lib); context.getVariables().declareVariable("List.class", List.class); context.getVariables().declareVariable("NodeSet.class", NodeSet.class); } functions = new ClassFunctions(TestFunctions.class, "test"); typeConverter = TypeUtils.getTypeConverter(); } public void tearDown() { TypeUtils.setTypeConverter(typeConverter); } public void testConstructorLookup() { Object[] args = new Object[] { new Integer(1), "x" }; Function func = functions.getFunction("test", "new", args); assertEquals( "test:new(1, x)", func.invoke(new Context(null), args).toString(), "foo=1; bar=x"); } public void testConstructorLookupWithExpressionContext() { Object[] args = new Object[] { "baz" }; Function func = functions.getFunction("test", "new", args); assertEquals( "test:new('baz')", func.invoke(new Context(new Integer(1)), args).toString(), "foo=1; bar=baz"); } public void testStaticMethodLookup() { Object[] args = new Object[] { new Integer(1), "x" }; Function func = functions.getFunction("test", "build", args); assertEquals( "test:build(1, x)", func.invoke(new Context(null), args).toString(), "foo=1; bar=x"); } public void testStaticMethodLookupWithConversion() { Object[] args = new Object[] { "7", new Integer(1)}; Function func = functions.getFunction("test", "build", args); assertEquals( "test:build('7', 1)", func.invoke(new Context(null), args).toString(), "foo=7; bar=1"); } public void testMethodLookup() { Object[] args = new Object[] { new TestFunctions()}; Function func = functions.getFunction("test", "getFoo", args); assertEquals( "test:getFoo($test, 1, x)", func.invoke(new Context(null), args).toString(), "0"); } public void testStaticMethodLookupWithExpressionContext() { Object[] args = new Object[0]; Function func = functions.getFunction("test", "path", args); assertEquals( "test:path()", func.invoke(new Context(new Integer(1)), args), "1"); } public void testMethodLookupWithExpressionContext() { Object[] args = new Object[] { new TestFunctions()}; Function func = functions.getFunction("test", "instancePath", args); assertEquals( "test:instancePath()", func.invoke(new Context(new Integer(1)), args), "1"); } public void testMethodLookupWithExpressionContextAndArgument() { Object[] args = new Object[] { new TestFunctions(), "*" }; Function func = functions.getFunction("test", "pathWithSuffix", args); assertEquals( "test:pathWithSuffix('*')", func.invoke(new Context(new Integer(1)), args), "1*"); } public void testAllocation() { // Allocate new object using the default constructor assertXPathValue(context, "string(test:new())", "foo=0; bar=null"); // Allocate new object using PackageFunctions and class name assertXPathValue( context, "string(jxpathtest:TestFunctions.new())", "foo=0; bar=null"); // Allocate new object using a fully qualified class name assertXPathValue( context, "string(" + TestFunctions.class.getName() + ".new())", "foo=0; bar=null"); // Allocate new object using a custom constructor assertXPathValue( context, "string(test:new(3, 'baz'))", "foo=3; bar=baz"); // Allocate new object using a custom constructor - type conversion assertXPathValue(context, "string(test:new('3', 4))", "foo=3; bar=4.0"); context.getVariables().declareVariable("A", "baz"); assertXPathValue( context, "string(test:new(2, $A, false))", "foo=2; bar=baz"); } public void testMethodCall() { assertXPathValue(context, "length('foo')", new Integer(3)); // We are just calling a method - prefix is ignored assertXPathValue(context, "call:substring('foo', 1, 2)", "o"); // Invoke a function implemented as a regular method assertXPathValue(context, "string(test:getFoo($test))", "4"); // Note that the prefix is ignored anyway, we are just calling a method assertXPathValue(context, "string(call:getFoo($test))", "4"); // We don't really need to supply a prefix in this case assertXPathValue(context, "string(getFoo($test))", "4"); // Method with two arguments assertXPathValue( context, "string(test:setFooAndBar($test, 7, 'biz'))", "foo=7; bar=biz"); } public void testCollectionMethodCall() { List list = new ArrayList(); list.add("foo"); context.getVariables().declareVariable("myList", list); assertXPathValue( context, "size($myList)", new Integer(1)); assertXPathValue( context, "size(beans)", new Integer(2)); context.getValue("add($myList, 'hello')"); assertEquals("After adding an element", 2, list.size()); JXPathContext context = JXPathContext.newContext(new ArrayList()); assertEquals("Extension function on root collection", "0", String .valueOf(context.getValue("size(/)"))); } public void testStaticMethodCall() { assertXPathValue( context, "string(test:build(8, 'goober'))", "foo=8; bar=goober"); // Call a static method using PackageFunctions and class name assertXPathValue( context, "string(jxpathtest:TestFunctions.build(8, 'goober'))", "foo=8; bar=goober"); // Call a static method with a fully qualified class name assertXPathValue( context, "string(" + TestFunctions.class.getName() + ".build(8, 'goober'))", "foo=8; bar=goober"); // Two ClassFunctions are sharing the same prefix. // This is TestFunctions2 assertXPathValue(context, "string(test:increment(8))", "9"); // See that a NodeSet gets properly converted to a string assertXPathValue(context, "test:string(/beans/name)", "Name 1"); } public void testExpressionContext() { // Execute an extension function for each node while searching // The function uses ExpressionContext to get to the current // node. assertXPathValue( context, "//.[test:isMap()]/Key1", "Value 1"); // The function gets all // nodes in the context that match the pattern. assertXPathValue( context, "count(//.[test:count(strings) = 3])", new Double(7)); // The function receives a collection of strings // and checks their type for testing purposes assertXPathValue( context, "test:count(//strings)", new Integer(21)); // The function receives a collection of pointers // and checks their type for testing purposes assertXPathValue( context, "test:countPointers(//strings)", new Integer(21)); // The function uses ExpressionContext to get to the current // pointer and returns its path. assertXPathValue( context, "/beans[contains(test:path(), '[2]')]/name", "Name 2"); } public void testCollectionReturn() { assertXPathValueIterator( context, "test:collection()/name", list("foo", "bar")); assertXPathPointerIterator( context, "test:collection()/name", list("/.[1]/name", "/.[2]/name")); assertXPathValue( context, "test:collection()/name", "foo"); assertXPathValue( context, "test:collection()/@name", "foo"); List list = new ArrayList(); list.add("foo"); list.add("bar"); context.getVariables().declareVariable("list", list); Object values = context.getValue("test:items($list)"); assertTrue("Return type: ", values instanceof Collection); assertEquals( "Return values: ", list, new ArrayList((Collection) values)); } public void testNodeSetReturn() { assertXPathValueIterator( context, "test:nodeSet()/name", list("Name 1", "Name 2")); assertXPathValueIterator( context, "test:nodeSet()", list(testBean.getBeans()[0], testBean.getBeans()[1])); assertXPathPointerIterator( context, "test:nodeSet()/name", list("/beans[1]/name", "/beans[2]/name")); assertXPathValueAndPointer( context, "test:nodeSet()/name", "Name 1", "/beans[1]/name"); assertXPathValueAndPointer( context, "test:nodeSet()/@name", "Name 1", "/beans[1]/@name"); assertEquals(2, ((Number) context.getValue("count(test:nodeSet())")).intValue()); assertXPathValue(context, "test:nodeSet()", testBean.getBeans()[0]); } public void testEstablishNodeSetBaseline() { assertXPathValue( context, "test:isInstance(//strings, $List.class)", Boolean.TRUE); assertXPathValue( context, "test:isInstance(//strings, $NodeSet.class)", Boolean.FALSE); } public void testBCNodeSetHack() { TypeUtils.setTypeConverter(new JXPath11CompatibleTypeConverter()); assertXPathValue( context, "test:isInstance(//strings, $List.class)", Boolean.FALSE); assertXPathValue( context, "test:isInstance(//strings, $NodeSet.class)", Boolean.TRUE); } private static class Context implements ExpressionContext { private Object object; public Context(Object object) { this.object = object; } public Pointer getContextNodePointer() { return NodePointer .newNodePointer(null, object, Locale.getDefault()); } public List getContextNodeList() { return null; } public JXPathContext getJXPathContext() { return null; } public int getPosition() { return 0; } } }