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