[Practicalxml-commits] SF.net SVN: practicalxml:[55] trunk/src
Brought to you by:
kdgregory
|
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-17 03:35:38
|
Revision: 55
http://practicalxml.svn.sourceforge.net/practicalxml/?rev=55&view=rev
Author: kdgregory
Date: 2008-12-17 03:35:33 +0000 (Wed, 17 Dec 2008)
Log Message:
-----------
add AbstractFunction
Added Paths:
-----------
trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java
trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java
Added: trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java
===================================================================
--- trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java (rev 0)
+++ trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java 2008-12-17 03:35:33 UTC (rev 55)
@@ -0,0 +1,396 @@
+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 org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+
+/**
+ * A base class for writing XPath functions that replaces much of the
+ * boilerplate code using a "template method" approach. Execution goes
+ * through the following three stages:
+ * <ol>
+ * <li> Initialization: the subclass method {@link #init} is invoked.
+ * <li> Input: for each argument, the type-appropriate {@link #processArg}
+ * method is called.
+ * <li> Completion: the subclass method {@link #getResult} is invoked, and
+ * its return value is returned from {@link #evaluate}.
+ * </ol>
+ * <p>
+ * Subclasses define their name, namespace, and the number of arguments
+ * they accept (which may be undefined).
+ * <p>
+ * To support thread safety, subclasses are expected to make use of a helper
+ * object that holds intermediate results. The helper object is created by
+ * <code>init()</code>, is an argument to and return from <code>processArg()
+ * </code>, and is an argument to <code>getResult()</code>. Subclasses are
+ * parameterized with the helper object type, and the default behavior of
+ * <code>getResult()</code> is to return the helper (this makes life easier
+ * for a function that takes zero or one arguments).
+ * <p>
+ * <em>Note:</em> in JDK 1.5, DOM objects implement both <code>Node</code>
+ * and <code>NodeList</code>. While this means that <code>processArg(Node)
+ * </code> would never be called as part of the normal dispatch loop (unless
+ * the implementation changes), it <em>is</em> called as part of the default
+ * behavior of <code>processArg(NodeList)</code>, and is given the first
+ * element in the list.
+ */
+public class AbstractFunction<T>
+implements XPathFunction
+{
+ /**
+ * Qualified name of this function.
+ */
+ final private QName _qname;
+
+
+ /**
+ * Minimum number of arguments, 0 if no minimum.
+ */
+ final private int _minArgCount;
+
+
+ /**
+ * Maximum number of arguments, Integer.MAX_VALUE if no maximum.
+ */
+ final private int _maxArgCount;
+
+
+ /**
+ * Constructor for a function that can take any number of arguments.
+ */
+ protected AbstractFunction(String nsUri, String localName)
+ {
+ _qname = new QName(nsUri, localName);
+ _minArgCount = 0;
+ _maxArgCount = Integer.MAX_VALUE;
+ }
+
+
+ /**
+ * Constructor for a function that has a fixed number of arguments.
+ */
+ protected AbstractFunction(String nsUri, String localName, int numArgs)
+ {
+ _qname = new QName(nsUri, localName);
+ _minArgCount = numArgs;
+ _maxArgCount = numArgs;
+ }
+
+
+ /**
+ * Constructor for a function that has a variable number of arguments.
+ */
+ protected AbstractFunction(String nsUri, String localName, int minArgs, int maxArgs)
+ {
+ _qname = new QName(nsUri, localName);
+ _minArgCount = minArgs;
+ _maxArgCount = maxArgs;
+ }
+
+
+//----------------------------------------------------------------------------
+// Public methods
+//----------------------------------------------------------------------------
+
+ /**
+ * Returns the qualified name of this function, consisting of name and
+ * namespace (but not prefix).
+ */
+ public QName getQName()
+ {
+ return _qname;
+ }
+
+
+ /**
+ * Returns the namespace URI for this function.
+ */
+ public String getNamespaceUri()
+ {
+ return _qname.getNamespaceURI();
+ }
+
+
+ /**
+ * Returns the name of this function.
+ */
+ public String getName()
+ {
+ return _qname.getLocalPart();
+ }
+
+
+ /**
+ * Returns the minimum number of arguments handled by this function.
+ */
+ public int getMinArgCount()
+ {
+ return _minArgCount;
+ }
+
+
+ /**
+ * Returns the maximum number of arguments handled by this function.
+ */
+ public int getMaxArgCount()
+ {
+ return _maxArgCount;
+ }
+
+
+ /**
+ * Determines whether this function is a match for a call to
+ * <code>XPathFunctionResolver.resolveFunction()</code>.
+ */
+ public boolean isMatch(QName qname, int arity)
+ {
+ return _qname.equals(qname)
+ && (arity >= _minArgCount)
+ && (arity <= _maxArgCount);
+ }
+
+
+//----------------------------------------------------------------------------
+// Implementation of XPathFunction
+//----------------------------------------------------------------------------
+
+ /**
+ * Invokes methods defined by the subclasses to evaluate the function.
+ * Will invoke the appropriate <code>processArg()</code> method in turn
+ * for each argument, then invokes <code>getResult()</code> to retrieve
+ * the function's result.
+ *
+ * @throws XPathFunctionException if the supplied argument list does
+ * not match the min/max for this function, or if any called
+ * method throws an exception.
+ */
+ public Object evaluate(List args)
+ throws XPathFunctionException
+ {
+ if (args == null)
+ args = Collections.EMPTY_LIST;
+
+ if ((args.size() < _minArgCount) || (args.size() > _maxArgCount))
+ throw new XPathFunctionException("illegal argument count: " + args.size());
+
+ try
+ {
+ T helper = init();
+ int idx = 0;
+ for (Object arg : args)
+ {
+ if (arg instanceof String)
+ helper = processArg(idx, (String)arg, helper);
+ else if (arg instanceof NodeList)
+ helper = processArg(idx, (NodeList)arg, helper);
+ else if (arg instanceof Node)
+ helper = processArg(idx, (Node)arg, helper);
+ else if (arg == null)
+ helper = processNullArg(idx, helper);
+ else
+ helper = processUnexpectedArg(idx, arg, helper);
+ idx++;
+ }
+ return getResult(helper);
+ }
+ catch (Exception e)
+ {
+ throw new XPathFunctionException(e);
+ }
+ }
+
+
+//----------------------------------------------------------------------------
+// Subclasses override these methods
+//----------------------------------------------------------------------------
+
+ /**
+ * Creates a helper object to preserve intermediate results. This object
+ * is passed to <code>processArg()</code> and <code>getResult()</code>.
+ * <p>
+ * The default implementation returns <code>null</code>.
+ * <p>
+ * Subclasses are permitted to throw any exception; it will be wrapped in
+ * an <code>XPathFunctionException</code>.
+ */
+ protected T init()
+ throws Exception
+ {
+ return null;
+ }
+
+
+ /**
+ * Processes a String argument.
+ *
+ * @param index Index of the argument, numbered from 0.
+ * @param value Value of the argument.
+ * @param helper Helper object to preserve intermediate results.
+ *
+ * @return The helper object (subclasses may return a replacement).
+ *
+ * @throws Subclasses are permitted to throw any exception. It will be
+ * wrapped in <code>XPathFunctionException</code> and cause
+ * function processing to abort.
+ */
+ protected T processArg(int index, String value, T helper)
+ throws Exception
+ {
+ return helper;
+ }
+
+
+ /**
+ * Processes a Number argument. Per the XPath spec, this should be a
+ * <code>Double</code>
+ *
+ * @param index Index of the argument, numbered from 0.
+ * @param value Value of the argument.
+ * @param helper Helper object to preserve intermediate results.
+ *
+ * @return The helper object (subclasses may return a replacement).
+ *
+ * @throws Subclasses are permitted to throw any exception. It will be
+ * wrapped in <code>XPathFunctionException</code> and cause
+ * function processing to abort.
+ */
+ protected T processArg(int index, Number value, T helper)
+ throws Exception
+ {
+ return helper;
+ }
+
+
+ /**
+ * Processes a Boolean argument.
+ *
+ * @param index Index of the argument, numbered from 0.
+ * @param value Value of the argument.
+ * @param helper Helper object to preserve intermediate results.
+ *
+ * @return The helper object (subclasses may return a replacement).
+ *
+ * @throws Subclasses are permitted to throw any exception. It will be
+ * wrapped in <code>XPathFunctionException</code> and cause
+ * function processing to abort.
+ */
+ protected T processArg(int index, Boolean value, T helper)
+ throws Exception
+ {
+ return helper;
+ }
+
+
+ /**
+ * Processes a Node argument. This function will be invoked by the
+ * default implementation of <code>processArg(NodeList)</code>.
+ *
+ * @param index Index of the argument, numbered from 0.
+ * @param value Value of the argument. May be <code>null</code>.
+ * @param helper Helper object to preserve intermediate results.
+ *
+ * @return The helper object (subclasses may return a replacement).
+ *
+ * @throws Subclasses are permitted to throw any exception. It will be
+ * wrapped in <code>XPathFunctionException</code> and cause
+ * function processing to abort.
+ */
+ protected T processArg(int index, Node value, T helper)
+ throws Exception
+ {
+ return helper;
+ }
+
+
+ /**
+ * Processes a NodeList argument.
+ * <p>
+ * The default implementation calls <code>processArg(Node)</code> with
+ * the first element in the list (<code>null</code> if the list is empty).
+ *
+ * @param index Index of the argument, numbered from 0.
+ * @param value Value of the argument.
+ * @param helper Helper object to preserve intermediate results.
+ *
+ * @return The helper object (subclasses may return a replacement).
+ *
+ * @throws Subclasses are permitted to throw any exception. It will be
+ * wrapped in <code>XPathFunctionException</code> and cause
+ * function processing to abort.
+ */
+ protected T processArg(int index, NodeList value, T helper)
+ throws Exception
+ {
+ return processArg(index, value.item(0), helper);
+ }
+
+
+ /**
+ * Processes a <code>null</code> argument — it's unclear whether
+ * this can ever happen. The default implementation throws <code>
+ * IllegalArgumentException</code>.
+ *
+ * @param index Index of the argument, numbered from 0.
+ * @param helper Helper object to preserve intermediate results.
+ *
+ * @return The helper object (subclasses may return a replacement).
+ *
+ * @throws Subclasses are permitted to throw any exception. It will be
+ * wrapped in <code>XPathFunctionException</code> and cause
+ * function processing to abort.
+ */
+ protected T processNullArg(int index, T helper)
+ throws Exception
+ {
+ throw new IllegalArgumentException("null argument: " + index);
+ }
+
+
+ /**
+ * Processes an argument that is not one of the defined types for XPath
+ * evaluation — it is unclear whether this can actually happen.
+ * The default implementation throws <code>IllegalArgumentException</code>.
+ *
+ * @param index Index of the argument, numbered from 0.
+ * @param value The argument value
+ * @param helper Helper object to preserve intermediate results.
+ *
+ * @return The helper object (subclasses may return a replacement).
+ *
+ * @throws Subclasses are permitted to throw any exception. It will be
+ * wrapped in <code>XPathFunctionException</code> and cause
+ * function processing to abort.
+ */
+ protected T processUnexpectedArg(int index, Object value, T helper)
+ throws Exception
+ {
+ throw new IllegalArgumentException(
+ "unexpected argument: " + index
+ + " (" + value.getClass().getName() + ")");
+ }
+
+
+ /**
+ * Returns the result of this invocation.
+ *
+ * @param helper Helper object to preserve intermediate results.
+ *
+ * @return The helper object (subclasses may return whatever they want).
+ *
+ * @throws Subclasses are permitted to throw any exception. It will be
+ * wrapped in <code>XPathFunctionException</code> and cause
+ * function processing to abort.
+ */
+ protected Object getResult(T helper)
+ throws Exception
+ {
+ return helper;
+ }
+}
Added: trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java
===================================================================
--- trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java (rev 0)
+++ trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java 2008-12-17 03:35:33 UTC (rev 55)
@@ -0,0 +1,320 @@
+package net.sf.practicalxml.xpath;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.xml.namespace.QName;
+import javax.xml.xpath.XPathFunctionException;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import net.sf.practicalxml.AbstractTestCase;
+import net.sf.practicalxml.DomUtil;
+
+
+public class TestAbstractFunction
+extends AbstractTestCase
+{
+//----------------------------------------------------------------------------
+// Support code
+//----------------------------------------------------------------------------
+
+ /**
+ * A mock function implementation that records its invocations, and
+ * passes a dummy helper object through the call chain (returning it
+ * as the invocation result).
+ */
+ private static class MyMockFunction
+ extends AbstractFunction<Object>
+ {
+ public Object _helper;
+ public int _initCalls;
+ public int _processStringCalls;
+ public int _processNumberCalls;
+ public int _processBooleanCalls;
+ public int _procesNodeCalls;
+ public int _procesNodeListCalls;
+ public int _getResultCalls;
+
+ public MyMockFunction(Object helper)
+ {
+ super("foo", "bar");
+ _helper = helper;
+ }
+
+ @Override
+ protected Object init() throws Exception
+ {
+ _initCalls++;
+ return _helper;
+ }
+
+ @Override
+ protected Object processArg(int index, String value, Object helper)
+ throws Exception
+ {
+ _processStringCalls++;
+ return helper;
+ }
+
+ @Override
+ protected Object processArg(int index, Number value, Object helper)
+ throws Exception
+ {
+ _processNumberCalls++;
+ return helper;
+ }
+
+ @Override
+ protected Object processArg(int index, Boolean value, Object helper)
+ throws Exception
+ {
+ _processBooleanCalls++;
+ return helper;
+ }
+
+ @Override
+ protected Object processArg(int index, Node value, Object helper)
+ throws Exception
+ {
+ _procesNodeCalls++;
+ return helper;
+ }
+
+ @Override
+ protected Object processArg(int index, NodeList value, Object helper)
+ throws Exception
+ {
+ _procesNodeListCalls++;
+ return helper;
+ }
+
+ @Override
+ protected Object getResult(Object helper)
+ {
+ _getResultCalls++;
+ assertSame(_helper, helper);
+ return _helper;
+ }
+ }
+
+
+//----------------------------------------------------------------------------
+// Test cases
+//----------------------------------------------------------------------------
+
+ public void testConstructionAndAccessors() throws Exception
+ {
+ QName testName = new QName("foo", "bar");
+
+ AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar");
+ assertEquals(testName, fn1.getQName());
+ assertEquals("foo", fn1.getNamespaceUri());
+ assertEquals("bar", fn1.getName());
+ assertEquals(0, fn1.getMinArgCount());
+ assertEquals(Integer.MAX_VALUE, fn1.getMaxArgCount());
+
+ assertTrue(fn1.isMatch(testName, 0));
+ assertTrue(fn1.isMatch(testName, 1));
+ assertTrue(fn1.isMatch(testName, Integer.MAX_VALUE));
+
+ AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "bar", 5);
+ assertEquals(testName, fn2.getQName());
+ assertEquals("foo", fn2.getNamespaceUri());
+ assertEquals("bar", fn2.getName());
+ assertEquals(5, fn2.getMinArgCount());
+ assertEquals(5, fn2.getMaxArgCount());
+
+ assertFalse(fn2.isMatch(testName, 0));
+ assertFalse(fn2.isMatch(testName, 1));
+ assertTrue(fn2.isMatch(testName, 5));
+ assertFalse(fn2.isMatch(testName, 6));
+ assertFalse(fn2.isMatch(testName, Integer.MAX_VALUE));
+
+ AbstractFunction<Object> fn3 = new AbstractFunction<Object>("foo", "bar", 1, 5);
+ assertEquals(testName, fn3.getQName());
+ assertEquals("foo", fn3.getNamespaceUri());
+ assertEquals("bar", fn3.getName());
+ assertEquals(1, fn3.getMinArgCount());
+ assertEquals(5, fn3.getMaxArgCount());
+
+ assertFalse(fn3.isMatch(testName, 0));
+ assertTrue(fn3.isMatch(testName, 1));
+ assertTrue(fn3.isMatch(testName, 5));
+ assertFalse(fn3.isMatch(testName, 6));
+ assertFalse(fn3.isMatch(testName, Integer.MAX_VALUE));
+ }
+
+
+ public void testEvaluateInvalidArgCount() throws Exception
+ {
+ try
+ {
+ new AbstractFunction<Object>("foo", "bar", 5).evaluate(
+ Arrays.asList(new String[] {"foo", "bar"}));
+ fail("invalid argument count");
+ }
+ catch (XPathFunctionException e)
+ {
+ // success
+ }
+ }
+
+
+ public void testEvaluateNullArglist() throws Exception
+ {
+ MyMockFunction fn = new MyMockFunction("zippy");
+ assertEquals("zippy", fn.evaluate(null));
+ assertEquals(1, fn._initCalls);
+ assertEquals(0, fn._processStringCalls);
+ assertEquals(0, fn._processNumberCalls);
+ assertEquals(0, fn._processBooleanCalls);
+ assertEquals(0, fn._procesNodeCalls);
+ assertEquals(0, fn._procesNodeListCalls);
+ assertEquals(1, fn._getResultCalls);
+ }
+
+
+ public void testEvaluateNullArgument() throws Exception
+ {
+ List args = Arrays.asList(new Object[] {null});
+ MyMockFunction fn = new MyMockFunction("zippy");
+ try
+ {
+ fn.evaluate(Arrays.asList(args));
+ fail("evaluated null argument");
+ }
+ catch (XPathFunctionException e)
+ {
+ // success
+ }
+ }
+
+
+ public void testEvaluateUnsupportedArgumentType() throws Exception
+ {
+ List args = Arrays.asList(new Object[] {new Exception()});
+ MyMockFunction fn = new MyMockFunction("zippy");
+ try
+ {
+ fn.evaluate(args);
+ fail("evaluated unsupported argument type");
+ }
+ catch (XPathFunctionException e)
+ {
+ // success
+ }
+ }
+
+
+ public void testEvaluateString() throws Exception
+ {
+ List args = Arrays.asList(new Object[] {"foo", "bar"});
+ MyMockFunction fn = new MyMockFunction("zippy");
+
+ assertEquals("zippy", fn.evaluate(args));
+ assertEquals(1, fn._initCalls);
+ assertEquals(2, fn._processStringCalls);
+ assertEquals(0, fn._processNumberCalls);
+ assertEquals(0, fn._processBooleanCalls);
+ assertEquals(0, fn._procesNodeCalls);
+ assertEquals(0, fn._procesNodeListCalls);
+ assertEquals(1, fn._getResultCalls);
+ }
+
+
+ public void testEvaluateNode() throws Exception
+ {
+ // since any JDK Node implementation is also a NodeList, we have to
+ // get tricky
+ Node node = (Node)Proxy.newProxyInstance(
+ Node.class.getClassLoader(),
+ new Class[] {Node.class},
+ new InvocationHandler()
+ {
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable
+ {
+ return null;
+ }
+ });
+
+ List args = Arrays.asList(new Object[] {node});
+ MyMockFunction fn = new MyMockFunction("zippy");
+
+ assertEquals("zippy", fn.evaluate(args));
+ assertEquals(1, fn._initCalls);
+ assertEquals(0, fn._processStringCalls);
+ assertEquals(0, fn._processNumberCalls);
+ assertEquals(0, fn._processBooleanCalls);
+ assertEquals(1, fn._procesNodeCalls);
+ assertEquals(0, fn._procesNodeListCalls);
+ assertEquals(1, fn._getResultCalls);
+ }
+
+
+ public void testEvaluateNodeList() throws Exception
+ {
+ List args = Arrays.asList(new Object[] {DomUtil.newDocument("foo").getChildNodes()});
+ MyMockFunction fn = new MyMockFunction("zippy");
+
+ assertEquals("zippy", fn.evaluate(args));
+ assertEquals(1, fn._initCalls);
+ assertEquals(0, fn._processStringCalls);
+ assertEquals(0, fn._processNumberCalls);
+ assertEquals(0, fn._processBooleanCalls);
+ assertEquals(0, fn._procesNodeCalls);
+ assertEquals(1, fn._procesNodeListCalls);
+ assertEquals(1, fn._getResultCalls);
+ }
+
+
+ public void testDefaultNodeListBehavior()
+ throws Exception
+ {
+ final Element root = DomUtil.newDocument("foo");
+ final Element child1 = DomUtil.appendChild(root, "bar");
+ final Element child2 = DomUtil.appendChild(root, "baz");
+
+ new AbstractFunction<Object>("argle", "bargle")
+ {
+ @Override
+ protected Object processArg(int index, NodeList value, Object helper)
+ throws Exception
+ {
+ assertEquals(2, value.getLength());
+ return super.processArg(index, value, helper);
+ }
+
+ @Override
+ protected Object processArg(int index, Node value, Object helper)
+ throws Exception
+ {
+ assertSame(child1, value);
+ return null;
+ }
+ }.evaluate(Arrays.asList(new Object[] {root.getChildNodes()}));
+ }
+
+
+ public void testDefaultGetResultBehavior()
+ throws Exception
+ {
+ final Object helper = new Object();
+ AbstractFunction<Object> fn = new AbstractFunction<Object>("argle", "bargle")
+ {
+ @Override
+ protected Object init() throws Exception
+ {
+ return helper;
+ }
+ };
+
+ assertSame(helper, fn.evaluate(Collections.<String>emptyList()));
+ }
+}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|