[Practicalxml-commits] SF.net SVN: practicalxml:[58] trunk/src
Brought to you by:
kdgregory
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-20 11:29:20
|
Revision: 58 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=58&view=rev Author: kdgregory Date: 2008-12-20 11:29:17 +0000 (Sat, 20 Dec 2008) Log Message: ----------- add FunctionResolver update XPathWrapper to use it Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/xpath/FunctionResolver.java trunk/src/test/java/net/sf/practicalxml/xpath/TestFunctionResolver.java Modified: trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java 2008-12-17 14:03:16 UTC (rev 57) +++ trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java 2008-12-20 11:29:17 UTC (rev 58) @@ -11,7 +11,6 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFunction; -import javax.xml.xpath.XPathFunctionResolver; import javax.xml.xpath.XPathVariableResolver; import org.w3c.dom.Document; @@ -19,6 +18,8 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import net.sf.practicalxml.xpath.AbstractFunction; +import net.sf.practicalxml.xpath.FunctionResolver; import net.sf.practicalxml.xpath.NamespaceResolver; @@ -34,7 +35,7 @@ private final String _expr; private final NamespaceResolver _nsResolver = new NamespaceResolver(); private Map<QName,Object> _variables = new HashMap<QName,Object>(); - private Map<QName,XPathFunction> _functions = new HashMap<QName,XPathFunction>(); + private FunctionResolver _functions = new FunctionResolver(); private XPathExpression _compiled; @@ -129,22 +130,127 @@ /** - * Binds a function to this expression, replacing any previous binding - * for the specified name. Note that <code>XPathFunctionResolver</code> - * can support multiple functions with the same name; we don't. + * Binds an {@link net.sf.practicalxml.xpath.AbstractFunction} to this + * expression. Subsequent calls to this method with the same name will + * silently replace the binding. * <p> * Per the JDK documentation, user-defined functions must occupy their - * own namespace. You will need to add a namespace binding along with - * your function binding. + * own namespace. You must bind a namespace for this function, and use + * the bound prefix to reference it in your expression. Alternatively, + * you can call the variant of <code>bindFunction()</code> that binds + * a prefix to the function's namespace. * - * @param name The fully-qualified name for this binding. + * @param func The function. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(AbstractFunction func) + { + _functions.addFunction(func); + return this; + } + + + /** + * Binds an {@link net.sf.practicalxml.xpath.AbstractFunction} to this + * expression, along with the prefix used to access that function. + * Subsequent calls to this method with the same name will silently + * replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. This method will retrieve the namespace from the + * function, and bind it to the passed prefix. + * + * @param func The function. + * @param prefix The prefix to bind to this function's namespace. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(AbstractFunction func, String prefix) + { + _functions.addFunction(func); + return bindNamespace(prefix, func.getNamespaceUri()); + } + + + /** + * Binds a standard <code>XPathFunction</code> to this expression, + * handling any number of arguments. Subsequent calls to this method + * with the same name will silently replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. If the qualified name that you pass to this method + * includes a prefix, the associated namespace will be bound to that + * prefix. If not, you must bind the namespace explicitly. In either + * case, you must refer to the function in your expression using a + * bound prefix. + * + * @param name The qualified name for this function. Must contain + * a name and namespace, may contain a prefix. * @param func The function to bind to this name. * * @return The wrapper, so that calls may be chained. */ public XPathWrapper bindFunction(QName name, XPathFunction func) { - _functions.put(name, func); + return bindFunction(name, func, 0, Integer.MAX_VALUE); + } + + + /** + * Binds a standard <code>XPathFunction</code> to this expression, + * handling a specific number of arguments. Subsequent calls to this + * method with the same name and arity will silently replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. If the qualified name that you pass to this method + * includes a prefix, the associated namespace will be bound to that + * prefix. If not, you must bind the namespace explicitly. In either + * case, you must refer to the function in your expression using a + * bound prefix. + * + * @param name The qualified name for this function. Must contain + * a name and namespace, may contain a prefix. + * @param func The function to bind to this name. + * @param arity The number of arguments accepted by this function. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(QName name, XPathFunction func, int arity) + { + return bindFunction(name, func, arity, arity); + } + + + /** + * Binds a standard <code>XPathFunction</code> to this expression, + * handling a specific range of arguments. Subsequent calls to this + * method with the same name and range of arguments will silently + * replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. If the qualified name that you pass to this method + * includes a prefix, the associated namespace will be bound to that + * prefix. If not, you must bind the namespace explicitly. In either + * case, you must refer to the function in your expression using a + * bound prefix. + * + * @param name The qualified name for this function. Must contain + * a name and namespace, may contain a prefix. + * @param func The function to bind to this name. + * @param minArity The minimum number of arguments accepted by this + * function. + * @param maxArity The maximum number of arguments accepted by this + * function. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(QName name, XPathFunction func, + int minArity, int maxArity) + { + _functions.addFunction(func, name, minArity, maxArity); + if (!"".equals(name.getPrefix())) + bindNamespace(name.getPrefix(), name.getNamespaceURI()); return this; } @@ -335,7 +441,7 @@ XPath xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(_nsResolver); xpath.setXPathVariableResolver(new MyVariableResolver()); - xpath.setXPathFunctionResolver(new MyFunctionResolver()); + xpath.setXPathFunctionResolver(_functions); _compiled = xpath.compile(_expr); } catch (XPathExpressionException ee) @@ -356,18 +462,4 @@ return _variables.get(name); } } - - - /** - * Resolver for function references. Note that we ignore the "arity" - * argument, meaning that there's a single binding per name. - */ - private class MyFunctionResolver - implements XPathFunctionResolver - { - public XPathFunction resolveFunction(QName name, int arity) - { - return _functions.get(name); - } - } } Modified: trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java 2008-12-17 14:03:16 UTC (rev 57) +++ trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java 2008-12-20 11:29:17 UTC (rev 58) @@ -67,9 +67,7 @@ */ protected AbstractFunction(String nsUri, String localName) { - _qname = new QName(nsUri, localName); - _minArgCount = 0; - _maxArgCount = Integer.MAX_VALUE; + this(nsUri, localName, 0, Integer.MAX_VALUE); } @@ -78,9 +76,7 @@ */ protected AbstractFunction(String nsUri, String localName, int numArgs) { - _qname = new QName(nsUri, localName); - _minArgCount = numArgs; - _maxArgCount = numArgs; + this(nsUri, localName, numArgs, numArgs); } @@ -89,7 +85,16 @@ */ protected AbstractFunction(String nsUri, String localName, int minArgs, int maxArgs) { - _qname = new QName(nsUri, localName); + this(new QName(nsUri, localName), minArgs, maxArgs); + } + + + /** + * Base constructor. + */ + protected AbstractFunction(QName qname, int minArgs, int maxArgs) + { + _qname = qname; _minArgCount = minArgs; _maxArgCount = maxArgs; } @@ -152,12 +157,25 @@ public boolean isMatch(QName qname, int arity) { return _qname.equals(qname) - && (arity >= _minArgCount) - && (arity <= _maxArgCount); + && (arity >= _minArgCount) + && (arity <= _maxArgCount); } + + /** + * Determines whether this function can handle the specified number of + * arguments. This is used by {@link FunctionResolver}, which already + * knows that the QName matches. + */ + public boolean isArityMatch(int arity) + { + return (arity >= _minArgCount) + && (arity <= _maxArgCount); + } + + //---------------------------------------------------------------------------- -// Implementation of Comparable +// Implementation of Comparable, Object overrides //---------------------------------------------------------------------------- /** @@ -197,6 +215,40 @@ } + /** + * Two instances are considered equal if they have the same qualified + * name and accept the same range of arguments. This supports use with + * the collections framework, which is the only place that you should + * be checking equality. + */ + @Override + public final boolean equals(Object obj) + { + if (this == obj) + return true; + else if (obj instanceof AbstractFunction<?>) + { + AbstractFunction<?> that = (AbstractFunction<?>)obj; + return this._qname.equals(that._qname) + && this._minArgCount == that._minArgCount + && this._maxArgCount == that._maxArgCount; + } + else + return false; + } + + + /** + * Hashcode is based on the qualified name, does not consider argument + * count. + */ + @Override + public final int hashCode() + { + return _qname.hashCode(); + } + + //---------------------------------------------------------------------------- // Implementation of XPathFunction //---------------------------------------------------------------------------- @@ -376,6 +428,22 @@ * Processes a <code>null</code> argument — it's unclear whether * this can ever happen. The default implementation throws <code> * IllegalArgumentException</code>. + + + @Override + public boolean equals(Object obj) + { + // FIXME - implement + return super.equals(obj); + } + + + @Override + public int hashCode() + { + // FIXME - implement + return super.hashCode(); + } * * @param index Index of the argument, numbered from 0. * @param helper Helper object to preserve intermediate results. Added: trunk/src/main/java/net/sf/practicalxml/xpath/FunctionResolver.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/FunctionResolver.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/FunctionResolver.java 2008-12-20 11:29:17 UTC (rev 58) @@ -0,0 +1,266 @@ +package net.sf.practicalxml.xpath; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; + +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import javax.xml.xpath.XPathFunctionResolver; + + +/** + * An <code>XPathFunctionResolver</code> designed for use with subclasses of + * {@link AbstractFunction} (but will work with any <code>XPathFunction</code>). + * Functions are resolved based on name, namespace, and arity. Where multiple + * eligible functions have the same name and namespace, the comparison rules of + * <code>AbstractFunction</code> are used to pick the most appropriate function: + * smaller range of accepted arguments first, followed by lower minimum argument + * count. + * <p> + * This object follows the builder pattern: <code>addFunction()</code> returns + * the object itself, so that calls may be chained. + */ +public class FunctionResolver +implements XPathFunctionResolver +{ + private Map<QName,FunctionHolder> _table = new HashMap<QName,FunctionHolder>(); + +//---------------------------------------------------------------------------- +// Public Methods +//---------------------------------------------------------------------------- + + /** + * Adds a subclass of {@link AbstractFunction} to this resolver. This + * function provides its own information about name, namespace, and + * supported number of arguments. + * <p> + * If the same function has already been added, will silently replace it. + * + * @return The resolver, so that calls may be chained. + */ + public FunctionResolver addFunction(AbstractFunction<?> func) + { + FunctionHolder holder = _table.get(func.getQName()); + if (holder == null) + { + holder = new FunctionHolder(func); + _table.put(func.getQName(), holder); + } + else + { + holder.put(func); + } + return this; + } + + + /** + * Adds a normal <code>XPathFunction</code> to this resolver, without + * specifying argument count. This function will be chosen for any number + * of arguments, provided that there is not a more-specific binding). + * <p> + * If a function has already been added with the same name and argument + * range, this call will silently replace it. + * <p> + * Note: the resolver wraps the passed function with a decorator that + * allows it to be managed. The resolver will return this decorator + * object rather than the original function. + * + * @return The resolver, so that calls may be chained. + */ + public FunctionResolver addFunction(XPathFunction func, QName name) + { + return addFunction(func, name, 0, Integer.MAX_VALUE); + } + + + /** + * Adds a normal <code>XPathFunction</code> to this resolver. Specifies + * the exact number of arguments for which this function will be chosen. + * <p> + * If a function has already been added with the same name and argument + * range, this call will silently replace it. + * <p> + * Note: the resolver wraps the passed function with a decorator that + * allows it to be managed. The resolver will return this decorator + * object rather than the original function. + * + * @return The resolver, so that calls may be chained. + */ + public FunctionResolver addFunction(XPathFunction func, QName name, int argCount) + { + return addFunction(func, name, argCount, argCount); + } + + + /** + * Adds a normal <code>XPathFunction</code> to this resolver. Specifies + * the minimum and maximum number of arguments for which this function + * will be chosen. + * <p> + * If a function has already been added with the same name and argument + * range, this call will silently replace it. + * <p> + * Note: the resolver wraps the passed function with a decorator that + * allows it to be managed. The resolver will return this decorator + * object rather than the original function. + * + * @return The resolver, so that calls may be chained. + */ + public FunctionResolver addFunction(XPathFunction func, QName name, int minArgCount, int maxArgCount) + { + return addFunction(new StandardFunctionAdapter(func, name, minArgCount, maxArgCount)); + } + + +//---------------------------------------------------------------------------- +// XPathFunctionResolver +//---------------------------------------------------------------------------- + + /** + * Picks the "most eligble" function of those bound to this resolver. + * If unable to find any function that matches, returns <code>null</code>. + */ + public XPathFunction resolveFunction(QName functionName, int arity) + { + FunctionHolder holder = _table.get(functionName); + return (holder != null) ? holder.get(arity) + : null; + } + + +//---------------------------------------------------------------------------- +// Object overrides +//---------------------------------------------------------------------------- + + /** + * Two instances are equal if they have the same number of functions, + * and each has a counterpart with the same QName and arity range (but + * not the same implementation). + */ + @Override + public final boolean equals(Object obj) + { + if (this == obj) + return true; + else if (obj instanceof FunctionResolver) + return this._table.equals(((FunctionResolver)obj)._table); + else + return false; + } + + + @Override + public final int hashCode() + { + // I suspect this is a very inefficient way to compute hashcode, + // but this object should never be stored in a hashing structure + // -- if you need to do that, come up with a better implementation + return _table.keySet().hashCode(); + } + + +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + /** + * Holder for AbstractFunction instances. Can deal with 1 or N, and + * pick the appropriate one for a particular evaluation. + */ + private static class FunctionHolder + { + private AbstractFunction<?> _onlyOne; + private TreeSet<AbstractFunction<?>> _hasMany; + + public FunctionHolder(AbstractFunction<?> initial) + { + _onlyOne = initial; + } + + public void put(AbstractFunction<?> func) + { + if (_hasMany != null) + { + // remove old implementation if it exists + _hasMany.remove(func); + _hasMany.add(func); + } + else if (_onlyOne.equals(func)) + { + _onlyOne = func; + } + else + { + _hasMany = new TreeSet<AbstractFunction<?>>(); + _hasMany.add(func); + _hasMany.add(_onlyOne); + _onlyOne = null; + } + } + + public AbstractFunction<?> get(int arity) + { + if (_onlyOne != null) + { + return (_onlyOne.isArityMatch(arity)) + ? _onlyOne + : null; + } + + for (AbstractFunction<?> func : _hasMany) + { + if (func.isArityMatch(arity)) + return func; + } + + return null; + } + + @Override + public boolean equals(Object obj) + { + // if this ever fails, someone needs to fix their code + FunctionHolder that = (FunctionHolder)obj; + + return (_onlyOne != null) + ? this._onlyOne.equals(that._onlyOne) + : this._hasMany.equals(that._hasMany); + } + + @Override + public int hashCode() + { + // I know this will never be stored as the key of a hash-table, + // but I want to keep static analysis tools quiet + return 0; + } + } + + + /** + * Adapter for standard XPathFunction instances, allowing them to be + * managed by this resolver. + */ + private static class StandardFunctionAdapter + extends AbstractFunction<Object> + { + final XPathFunction _func; + + public StandardFunctionAdapter(XPathFunction func, QName name, int minArgCount, int maxArgCount) + { + super(name, minArgCount, maxArgCount); + _func = func; + } + + @Override + public Object evaluate(List args) + throws XPathFunctionException + { + return _func.evaluate(args); + } + } +} Modified: trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java 2008-12-17 14:03:16 UTC (rev 57) +++ trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java 2008-12-20 11:29:17 UTC (rev 58) @@ -5,6 +5,8 @@ import javax.xml.xpath.XPathFunction; import javax.xml.xpath.XPathFunctionException; +import net.sf.practicalxml.xpath.AbstractFunction; + import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -50,7 +52,42 @@ // Support Code //---------------------------------------------------------------------------- + /** + * A standard XPath function implementation that returns the namespace + * of the first selected node. + */ + private static class MyStandardFunction + implements XPathFunction + { + public Object evaluate(List args) + throws XPathFunctionException + { + NodeList arg = (NodeList)args.get(0); + return arg.item(0).getNamespaceURI(); + } + } + + /** + * An <code>AbstractFunction</code> implementation that returns the + * namespace of the first selected node. + */ + private static class MyAbstractFunction + extends AbstractFunction<String> + { + public MyAbstractFunction(String nsUri, String name) + { + super(nsUri, name); + } + + @Override + protected String processArg(int index, Node value, String helper) + throws Exception + { + return value.getNamespaceURI(); + } + } + //---------------------------------------------------------------------------- // Test Cases //---------------------------------------------------------------------------- @@ -158,31 +195,62 @@ } - public void testFunctions() throws Exception + public void testAbstractFunctions() throws Exception { - _child1.setAttribute("bar", "baz"); - _child2.setAttribute("bar", "bargle"); + XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.)") + .bindNamespace("ns", NS1) + .bindFunction(new MyAbstractFunction(NS1, "myfunc")); - XPathFunction myfunc = new XPathFunction() { - public Object evaluate(List args) - throws XPathFunctionException - { - NodeList arg = (NodeList)args.get(0); - Element asElem = (Element)arg.item(0); - return asElem.getAttribute("bar"); - } - }; + assertEquals("", xpath1.evaluateAsString(_child1)); + assertEquals(NS1, xpath1.evaluateAsString(_child2)); - XPathWrapper xpath = new XPathWrapper("ns:myfunc(.)") - .bindNamespace("ns", NS1) - .bindFunction(new QName(NS1, "myfunc"), myfunc); + XPathWrapper xpath2 = new XPathWrapper("ns:myfunc(.)") + .bindFunction(new MyAbstractFunction(NS1, "myfunc"), "ns"); - assertEquals("baz", xpath.evaluateAsString(_child1)); - assertEquals("bargle", xpath.evaluateAsString(_child2)); + assertEquals("", xpath2.evaluateAsString(_child1)); + assertEquals(NS1, xpath2.evaluateAsString(_child2)); } + public void testStandardFunctions() throws Exception + { + XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.)") + .bindFunction(new QName(NS1, "myfunc", "ns"), + new MyStandardFunction()); + assertEquals("", xpath1.evaluateAsString(_child1)); + assertEquals(NS1, xpath1.evaluateAsString(_child2)); + + XPathWrapper xpath2 = new XPathWrapper("ns:myfunc(.,.)") + .bindFunction(new QName(NS2, "myfunc", "ns"), + new MyStandardFunction(), + 2); + + assertEquals("", xpath2.evaluateAsString(_child1)); + assertEquals(NS1, xpath2.evaluateAsString(_child2)); + } + + + public void testUnresolvableFunction() throws Exception + { + // we call with two arguments, it only gets resolved for one + XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.,.)") + .bindFunction(new QName(NS2, "myfunc", "ns"), + new MyStandardFunction(), + 1); + try + { + xpath1.evaluateAsString(_child1); + fail("didn't throw even though arity was wrong"); + } + catch (XmlException ee) + { + // success + } + } + + + public void testEqualsAndHashCode() throws Exception { Object obj1a = new XPathWrapper("//foo"); Modified: trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java 2008-12-17 14:03:16 UTC (rev 57) +++ trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java 2008-12-20 11:29:17 UTC (rev 58) @@ -359,4 +359,31 @@ assertEquals(1, fn3.compareTo(fn4)); assertEquals(-1, fn4.compareTo(fn3)); } + + public void testEqualsAndHashcode() throws Exception + { + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar", 1, 4); + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "bar", 3); + AbstractFunction<Object> fn3 = new AbstractFunction<Object>("foo", "bar", 3); + AbstractFunction<Object> fn4 = new AbstractFunction<Object>("foo", "baz", 3); + AbstractFunction<Object> fn5 = new AbstractFunction<Object>("fzz", "bar", 3); + + assertFalse(fn1.equals(fn2)); + assertFalse(fn2.equals(fn1)); + + assertTrue(fn2.equals(fn3)); + assertTrue(fn3.equals(fn2)); + + assertFalse(fn2.equals(fn4)); + assertFalse(fn4.equals(fn2)); + + assertFalse(fn2.equals(fn5)); + assertFalse(fn5.equals(fn2)); + + assertEquals(fn1.hashCode(), fn2.hashCode()); + assertEquals(fn2.hashCode(), fn3.hashCode()); + + // as of JDK 1.5, this is known to be true; it shouldn't change + assertTrue(fn1.hashCode() != fn4.hashCode()); + } } Added: trunk/src/test/java/net/sf/practicalxml/xpath/TestFunctionResolver.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/TestFunctionResolver.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/xpath/TestFunctionResolver.java 2008-12-20 11:29:17 UTC (rev 58) @@ -0,0 +1,227 @@ +package net.sf.practicalxml.xpath; + +import java.util.Collections; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; + +import net.sf.practicalxml.AbstractTestCase; + + +public class TestFunctionResolver +extends AbstractTestCase +{ +//---------------------------------------------------------------------------- +// Support Code +//---------------------------------------------------------------------------- + + private static class MockStandardFunction + implements XPathFunction + { + public int _calls; + + public Object evaluate(List args) throws XPathFunctionException + { + _calls++; + return null; + } + } + + +//---------------------------------------------------------------------------- +// Test Cases +//---------------------------------------------------------------------------- + + public void testSingleAbstractFunctionMaxRange() throws Exception + { + AbstractFunction<Object> fn = new AbstractFunction<Object>("foo", "bar"); + + FunctionResolver resolver = new FunctionResolver(); + assertSame(resolver, resolver.addFunction(fn)); + + assertSame(fn, resolver.resolveFunction(new QName("foo", "bar"), 0)); + assertSame(fn, resolver.resolveFunction(new QName("foo", "bar"), 1)); + assertSame(fn, resolver.resolveFunction(new QName("foo", "bar"), 10)); + } + + + public void testMultipleAbstractFunctionMaxRange() throws Exception + { + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar"); + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "baz"); + + FunctionResolver resolver = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2); + + assertSame(fn1, resolver.resolveFunction(new QName("foo", "bar"), 1)); + assertSame(fn2, resolver.resolveFunction(new QName("foo", "baz"), 1)); + } + + + public void testSingleAbstractFunctionMultiRange() throws Exception + { + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar"); + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "bar", 1); + AbstractFunction<Object> fn3 = new AbstractFunction<Object>("foo", "bar", 2, 5); + + FunctionResolver resolver = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2) + .addFunction(fn3); + + assertSame(fn1, resolver.resolveFunction(new QName("foo", "bar"), 0)); + assertSame(fn2, resolver.resolveFunction(new QName("foo", "bar"), 1)); + assertSame(fn3, resolver.resolveFunction(new QName("foo", "bar"), 2)); + assertSame(fn3, resolver.resolveFunction(new QName("foo", "bar"), 5)); + assertSame(fn1, resolver.resolveFunction(new QName("foo", "bar"), 6)); + } + + + public void testOverwriteOnAdd() throws Exception + { + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar", 1); + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "bar", 1); + AbstractFunction<Object> fn3 = new AbstractFunction<Object>("foo", "bar", 1); + + FunctionResolver resolver = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2); + + assertSame(fn2, resolver.resolveFunction(new QName("foo", "bar"), 1)); + + resolver.addFunction(fn3); + assertSame(fn3, resolver.resolveFunction(new QName("foo", "bar"), 1)); + } + + + public void testSingleStandardFunctionMaxRange() throws Exception + { + MockStandardFunction xfn = new MockStandardFunction(); + + FunctionResolver resolver = new FunctionResolver() + .addFunction(xfn, new QName("foo", "bar")); + + XPathFunction ret1 = resolver.resolveFunction(new QName("foo", "bar"), 0); + XPathFunction ret2 = resolver.resolveFunction(new QName("foo", "bar"), 0); + XPathFunction ret3 = resolver.resolveFunction(new QName("foo", "bar"), 0); + + assertNotNull(ret1); + assertSame(ret1, ret2); + assertSame(ret1, ret3); + + ret1.evaluate(Collections.EMPTY_LIST); + assertEquals(1, xfn._calls); + } + + + public void testSingleStandardFunctionMultiRange() throws Exception + { + MockStandardFunction xfn1 = new MockStandardFunction(); + MockStandardFunction xfn2 = new MockStandardFunction(); + MockStandardFunction xfn3 = new MockStandardFunction(); + + FunctionResolver resolver = new FunctionResolver() + .addFunction(xfn1, new QName("foo", "bar")) + .addFunction(xfn2, new QName("foo", "bar"), 1) + .addFunction(xfn3, new QName("foo", "bar"), 2, 5); + + resolver.resolveFunction(new QName("foo", "bar"), 0).evaluate(Collections.EMPTY_LIST); + assertEquals(1, xfn1._calls); + assertEquals(0, xfn2._calls); + assertEquals(0, xfn3._calls); + + resolver.resolveFunction(new QName("foo", "bar"), 1).evaluate(Collections.EMPTY_LIST); + assertEquals(1, xfn1._calls); + assertEquals(1, xfn2._calls); + assertEquals(0, xfn3._calls); + + resolver.resolveFunction(new QName("foo", "bar"), 2).evaluate(Collections.EMPTY_LIST); + assertEquals(1, xfn1._calls); + assertEquals(1, xfn2._calls); + assertEquals(1, xfn3._calls); + + resolver.resolveFunction(new QName("foo", "bar"), 5).evaluate(Collections.EMPTY_LIST); + assertEquals(1, xfn1._calls); + assertEquals(1, xfn2._calls); + assertEquals(2, xfn3._calls); + + resolver.resolveFunction(new QName("foo", "bar"), 6).evaluate(Collections.EMPTY_LIST); + assertEquals(2, xfn1._calls); + assertEquals(1, xfn2._calls); + assertEquals(2, xfn3._calls); + } + + + public void testUnresolvedFunctions() throws Exception + { + FunctionResolver resolver = new FunctionResolver() + .addFunction(new AbstractFunction<Object>("foo", "bar", 1)) + .addFunction(new AbstractFunction<Object>("fizz", "buzz", 1)) + .addFunction(new AbstractFunction<Object>("fizz", "buzz", 2)); + + assertNull(resolver.resolveFunction(new QName("f", "b"), 1)); + assertNull(resolver.resolveFunction(new QName("foo", "bar"), 2)); + assertNull(resolver.resolveFunction(new QName("fizz", "buzz"), 3)); + } + + + public void testEqualsAndHashcode() throws Exception + { + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar", 1); + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "bar", 1, 5); + AbstractFunction<Object> fn3 = new AbstractFunction<Object>("foo", "baz"); + + FunctionResolver resolv1 = new FunctionResolver() + .addFunction(fn1); + FunctionResolver resolv2 = new FunctionResolver() + .addFunction(fn1); + FunctionResolver resolv3 = new FunctionResolver() + .addFunction(fn2); + FunctionResolver resolv4 = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2); + FunctionResolver resolv5 = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2); + FunctionResolver resolv6 = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2) + .addFunction(fn3); + FunctionResolver resolv7 = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2) + .addFunction(fn3); + + // self and bogus + assertTrue(resolv1.equals(resolv1)); + assertFalse(resolv1.equals(new Object())); + assertFalse(resolv1.equals(null)); + + // single definition + assertTrue(resolv1.equals(resolv2)); + assertTrue(resolv2.equals(resolv1)); + + // multiple definitions, same QName + assertTrue(resolv4.equals(resolv5)); + assertTrue(resolv5.equals(resolv4)); + + // multiple definitions, multiple QNames + assertTrue(resolv6.equals(resolv7)); + assertTrue(resolv7.equals(resolv6)); + + // different arity ranges + assertFalse(resolv1.equals(resolv3)); + assertFalse(resolv3.equals(resolv1)); + + // partial overlap + assertFalse(resolv1.equals(resolv4)); + assertFalse(resolv4.equals(resolv1)); + + // hashcode - the different hashcodes are per test + assertTrue(resolv1.hashCode() == resolv2.hashCode()); + assertTrue(resolv1.hashCode() != resolv7.hashCode()); + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |