[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.
|