/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.jxpath.ri.model; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Locale; import org.apache.commons.jxpath.AbstractFactory; import org.apache.commons.jxpath.ClassFunctions; import org.apache.commons.jxpath.JXPathContext; import org.apache.commons.jxpath.JXPathTestCase; import org.apache.commons.jxpath.NestedTestBean; import org.apache.commons.jxpath.Pointer; import org.apache.commons.jxpath.ri.QName; import org.apache.commons.jxpath.ri.compiler.NodeNameTest; import org.apache.commons.jxpath.ri.compiler.TestFunctions; import org.apache.commons.jxpath.ri.model.beans.PropertyOwnerPointer; import org.apache.commons.jxpath.ri.model.beans.PropertyPointer; import org.apache.commons.jxpath.ri.model.dynabeans.DynaBeanModelTest; /** * Abstract superclass for Bean access with JXPath. * * @author Dmitri Plotnikov * @version $Revision: 652845 $ $Date: 2008-05-02 12:46:46 -0500 (Fri, 02 May 2008) $ */ public abstract class BeanModelTestCase extends JXPathTestCase { private JXPathContext context; public void setUp() { // if (context == null) { context = JXPathContext.newContext(createContextBean()); context.setLocale(Locale.US); context.setFactory(getAbstractFactory()); // } } protected abstract Object createContextBean(); protected abstract AbstractFactory getAbstractFactory(); /** * Test property iterators, the core of the graph traversal engine */ public void testIndividualIterators() { testIndividual(+1, 0, true, false, 0); testIndividual(-1, 0, true, false, 4); testIndividual(0, -1, true, true, 4); testIndividual(+1, -1, true, true, 4); testIndividual(-1, -1, true, true, 0); testIndividual(0, 1, true, false, 2); testIndividual(0, 1, true, true, 1); testIndividual(0, 0, false, false, 4); testIndividual(0, 0, false, true, 4); } private void testIndividual( int relativePropertyIndex, int offset, boolean useStartLocation, boolean reverse, int expected) { PropertyOwnerPointer root = (PropertyOwnerPointer) NodePointer.newNodePointer( new QName(null, "root"), createContextBean(), Locale.getDefault()); NodeIterator it; PropertyPointer start = null; if (useStartLocation) { start = root.getPropertyPointer(); start.setPropertyIndex( relativeProperty(start, relativePropertyIndex)); start.setIndex(offset); } it = root.childIterator( new NodeNameTest(new QName(null, "integers")), reverse, start); int size = 0; while (it.setPosition(it.getPosition() + 1)) { size++; } assertEquals( "ITERATIONS: Individual, relativePropertyIndex=" + relativePropertyIndex + ", offset=" + offset + ", useStartLocation=" + useStartLocation + ", reverse=" + reverse, expected, size); } /** * Test property iterators with multiple properties returned */ public void testMultipleIterators() { testMultiple(0, 0, true, false, 20); testMultiple(3, 0, true, false, 16); testMultiple(3, -1, true, true, 8); testMultiple(3, 0, true, true, 4); testMultiple(0, 0, false, false, 21); testMultiple(0, 0, false, true, 21); testMultiple(3, 1, true, false, 15); testMultiple(3, 3, true, false, 13); } private void testMultiple( int propertyIndex, int offset, boolean useStartLocation, boolean reverse, int expected) { PropertyOwnerPointer root = (PropertyOwnerPointer) NodePointer.newNodePointer( new QName(null, "root"), createContextBean(), Locale.getDefault()); NodeIterator it; PropertyPointer start = null; if (useStartLocation) { start = root.getPropertyPointer(); start.setPropertyIndex(propertyIndex); start.setIndex(offset); } it = root.childIterator(null, reverse, start); int size = 0; while (it.setPosition(it.getPosition() + 1)) { // System.err.println("LOC: " + it.getCurrentNodePointer()); size++; } assertEquals( "ITERATIONS: Multiple, propertyIndex=" + propertyIndex + ", offset=" + offset + ", useStartLocation=" + useStartLocation + ", reverse=" + reverse, expected, size); } private int relativeProperty(PropertyPointer holder, int offset) { String[] names = holder.getPropertyNames(); for (int i = 0; i < names.length; i++) { if (names[i].equals("integers")) { return i + offset; } } return -1; } public void testIteratePropertyArrayWithHasNext() { JXPathContext context = JXPathContext.newContext(createContextBean()); Iterator it = context.iteratePointers("/integers"); List actual = new ArrayList(); while (it.hasNext()) { actual.add(((Pointer) it.next()).asPath()); } assertEquals( "Iterating 'hasNext'/'next'<" + "/integers" + ">", list( "/integers[1]", "/integers[2]", "/integers[3]", "/integers[4]"), actual); } public void testIteratePropertyArrayWithoutHasNext() { JXPathContext context = JXPathContext.newContext(createContextBean()); Iterator it = context.iteratePointers("/integers"); List actual = new ArrayList(); for (int i = 0; i < 4; i++) { actual.add(it.next().toString()); } assertEquals( "Iterating 'next'<" + "/integers" + ">", list( "/integers[1]", "/integers[2]", "/integers[3]", "/integers[4]"), actual); } public void testIterateAndSet() { JXPathContext context = JXPathContext.newContext(createContextBean()); Iterator it = context.iteratePointers("beans/int"); int i = 5; while (it.hasNext()) { NodePointer pointer = (NodePointer) it.next(); pointer.setValue(new Integer(i++)); } it = context.iteratePointers("beans/int"); List actual = new ArrayList(); while (it.hasNext()) { actual.add(((Pointer) it.next()).getValue()); } assertEquals( "Iterating <" + "beans/int" + ">", list(new Integer(5), new Integer(6)), actual); } /** * Test contributed by Kate Dvortsova */ public void testIteratePointerSetValue() { JXPathContext context = JXPathContext.newContext(createContextBean()); assertXPathValue(context, "/beans[1]/name", "Name 1"); assertXPathValue(context, "/beans[2]/name", "Name 2"); // Test setting via context context.setValue("/beans[2]/name", "Name 2 set"); assertXPathValue(context, "/beans[2]/name", "Name 2 set"); // Restore original value context.setValue("/beans[2]/name", "Name 2"); assertXPathValue(context, "/beans[2]/name", "Name 2"); int iterCount = 0; Iterator iter = context.iteratePointers("/beans/name"); while (iter.hasNext()) { iterCount++; Pointer pointer = (Pointer) iter.next(); String s = (String) pointer.getValue(); s = s + "suffix"; pointer.setValue(s); assertEquals("pointer.getValue", s, pointer.getValue()); // fails right here, the value isn't getting set in the bean. assertEquals( "context.getValue", s, context.getValue(pointer.asPath())); } assertEquals("Iteration count", 2, iterCount); assertXPathValue(context, "/beans[1]/name", "Name 1suffix"); assertXPathValue(context, "/beans[2]/name", "Name 2suffix"); } public void testRoot() { assertXPathValueAndPointer(context, "/", context.getContextBean(), "/"); } public void testAxisAncestor() { // ancestor:: assertXPathValue(context, "int/ancestor::root = /", Boolean.TRUE); assertXPathValue( context, "count(beans/name/ancestor-or-self::node())", new Double(5)); assertXPathValue( context, "beans/name/ancestor-or-self::node()[3] = /", Boolean.TRUE); } public void testAxisChild() { assertXPathValue(context, "boolean", Boolean.FALSE); assertXPathPointer(context, "boolean", "/boolean"); assertXPathPointerIterator(context, "boolean", list("/boolean")); // Count elements in a child collection assertXPathValue(context, "count(set)", new Double(3)); // assertXPathValue(context,"boolean/class/name", "java.lang.Boolean"); // Child with namespace - should not find any assertXPathValueIterator(context, "foo:boolean", list()); // Count all children with a wildcard assertXPathValue(context, "count(*)", new Double(21)); // Same, constrained by node type = node() assertXPathValue(context, "count(child::node())", new Double(21)); } public void testAxisChildNestedBean() { // Nested bean assertXPathValue(context, "nestedBean/name", "Name 0"); assertXPathPointer(context, "nestedBean/name", "/nestedBean/name"); assertXPathPointerIterator( context, "nestedBean/name", list("/nestedBean/name")); } public void testAxisChildNestedCollection() { assertXPathValueIterator( context, "integers", list( new Integer(1), new Integer(2), new Integer(3), new Integer(4))); assertXPathPointer(context, "integers", "/integers"); assertXPathPointerIterator( context, "integers", list( "/integers[1]", "/integers[2]", "/integers[3]", "/integers[4]")); } public void testIndexPredicate() { assertXPathValue(context, "integers[2]", new Integer(2)); assertXPathPointer(context, "integers[2]", "/integers[2]"); assertXPathPointerIterator( context, "integers[2]", list("/integers[2]")); assertXPathValue(context, "beans[1]/name", "Name 1"); assertXPathPointer(context, "beans[1]/name", "/beans[1]/name"); assertXPathValueIterator( context, "beans[1]/strings", list("String 1", "String 2", "String 3")); assertXPathValueIterator( context, "beans/strings[2]", list("String 2", "String 2")); // Find the first match assertXPathValue(context, "beans/strings[2]", "String 2"); // Indexing in a set collected from a UnionContext assertXPathValue(context, "(beans/strings[2])[1]", "String 2"); } public void testAxisDescendant() { // descendant:: assertXPathValue(context, "count(descendant::node())", new Double(65)); // Should not find any descendants with name root assertXPathValue(context, "count(descendant::root)", new Double(0)); assertXPathValue(context, "count(descendant::name)", new Double(7)); } public void testAxisDescendantOrSelf() { // descendant-or-self:: assertXPathValueIterator( context, "descendant-or-self::name", set( "Name 1", "Name 2", "Name 3", "Name 6", "Name 0", "Name 5", "Name 4")); // Same - abbreviated syntax assertXPathValueIterator( context, "//name", set( "Name 1", "Name 2", "Name 3", "Name 6", "Name 0", "Name 5", "Name 4")); // See that it actually finds self assertXPathValue( context, "count(descendant-or-self::root)", new Double(1)); // Combine descendant-or-self:: and and self:: assertXPathValue(context, "count(nestedBean//.)", new Double(7)); // Combine descendant-or-self:: and and self::name assertXPathValue(context, "count(//self::beans)", new Double(2)); // Count all nodes in the tree assertXPathValue( context, "count(descendant-or-self::node())", new Double(66)); } public void testAxisFollowing() { // following:: assertXPathValue( context, "count(nestedBean/strings[2]/following::node())", new Double(21)); assertXPathValue( context, "count(nestedBean/strings[2]/following::strings)", new Double(7)); } public void testAxisFollowingSibling() { // following-sibling:: assertXPathValue( context, "count(/nestedBean/following-sibling::node())", new Double(8)); assertXPathValue( context, "count(/nestedBean/following-sibling::object)", new Double(1)); // Combine parent:: and following-sibling:: assertXPathValue( context, "count(/nestedBean/boolean/../following-sibling::node())", new Double(8)); assertXPathValue( context, "count(/nestedBean/boolean/../following-sibling::object)", new Double(1)); // Combine descendant:: and following-sibling:: assertXPathValue( context, "count(/descendant::boolean/following-sibling::node())", new Double(53)); assertXPathValue( context, "count(/descendant::boolean/following-sibling::name)", new Double(7)); } public void testAxisParent() { // parent:: assertXPathValue(context, "count(/beans/..)", new Double(1)); assertXPathValue(context, "count(//..)", new Double(9)); assertXPathValue(context, "count(//../..)", new Double(2)); assertXPathValueIterator( context, "//parent::beans/name", list("Name 1", "Name 2")); } public void testAxisPreceding() { // preceding:: assertXPathValue( context, "count(beans[2]/int/preceding::node())", new Double(8)); assertXPathValue( context, "count(beans[2]/int/preceding::boolean)", new Double(2)); } public void testAxisPrecedingSibling() { // preceding-sibling:: assertXPathValue( context, "count(/boolean/preceding-sibling::node())", new Double(2)); assertXPathValue( context, "count(/nestedBean/int/../preceding-sibling::node())", new Double(12)); assertXPathValue( context, "count(/descendant::int/preceding-sibling::node())", new Double(10)); } public void testAxisSelf() { // self:: assertXPathValue(context, "self::node() = /", Boolean.TRUE); assertXPathValue(context, "self::root = /", Boolean.TRUE); } public void testUnion() { // Union - note corrected document order assertXPathValueIterator( context, "integers | beans[1]/strings", list( "String 1", "String 2", "String 3", new Integer(1), new Integer(2), new Integer(3), new Integer(4))); assertXPathValue( context, "count((integers | beans[1]/strings)[contains(., '1')])", new Double(2)); assertXPathValue( context, "count((integers | beans[1]/strings)[name(.) = 'strings'])", new Double(3)); // Note that the following is different from "integer[2]" - // it is a filter expression assertXPathValue(context, "(integers)[2]", new Integer(2)); } public void testAxisAttribute() { // Attributes are just like children to beans assertXPathValue(context, "count(@*)", new Double(21.0)); // Unknown attribute assertXPathValueLenient(context, "@foo", null); } /** * Testing the pseudo-attribute "name" that java beans * objects appear to have. */ public void testAttributeName() { assertXPathValue(context, "nestedBean[@name = 'int']", new Integer(1)); assertXPathPointer( context, "nestedBean[@name = 'int']", "/nestedBean/int"); } public void testAttributeLang() { assertXPathValue(context, "@xml:lang", "en-US"); assertXPathValue(context, "count(@xml:*)", new Double(1)); assertXPathValue(context, "lang('en')", Boolean.TRUE); assertXPathValue(context, "lang('fr')", Boolean.FALSE); } public void testCoreFunctions() { assertXPathValue(context, "boolean(boolean)", Boolean.TRUE); assertXPathValue(context, "boolean(boolean = false())", Boolean.TRUE); assertXPathValue( context, "boolean(integers[position() < 3])", Boolean.TRUE); assertXPathValue( context, "boolean(integers[position() > 4])", Boolean.FALSE); assertXPathValue(context, "sum(integers)", new Double(10)); assertXPathValueAndPointer( context, "integers[last()]", new Integer(4), "/integers[4]"); assertXPathValueAndPointer( context, "//strings[last()]", "String 3", "/beans[1]/strings[3]"); } public void testBooleanPredicate() { // use child axis // bean[1]/int = 1 // bean[2]/int = 3 assertXPathValue(context, "beans[int > 2]/name", "Name 2"); assertXPathValueIterator( context, "beans[int > 2]/name", list("Name 2")); assertXPathValueIterator( context, "beans[int >= 1]/name", list("Name 1", "Name 2")); assertXPathValueIterator( context, "beans[int < 2]/name", list("Name 1")); assertXPathValueIterator( context, "beans[int <= 3]/name", list("Name 1", "Name 2")); assertXPathValueIterator( context, "beans[1]/strings[string-length() = 8]", list("String 1", "String 2", "String 3")); // use some fancy axis and the child axis in the predicate assertXPathValueIterator( context, "//self::node()[name = 'Name 0']/name", list("Name 0")); // use context-dependent function in the predicate assertXPathValue( context, "beans/strings[name(.)='strings'][2]", "String 2"); // use context-independent function in the predicate assertXPathValueIterator( context, "//self::node()[name(.) = concat('n', 'a', 'm', 'e')]", list( "Name 1", "Name 2", "Name 3", "Name 6", "Name 0", "Name 5", "Name 4")); assertXPathValueIterator( context, "integers[position()<3]", list(new Integer(1), new Integer(2))); context.getVariables().declareVariable( "temp", context.getValue("beans")); assertXPathValueIterator( context, "$temp[int < 2]/int", list(new Integer(1))); } public void testDocumentOrder() { assertDocumentOrder(context, "boolean", "int", -1); assertDocumentOrder(context, "integers[1]", "integers[2]", -1); assertDocumentOrder(context, "integers[1]", "integers[1]", 0); assertDocumentOrder(context, "nestedBean/int", "nestedBean", 1); assertDocumentOrder( context, "nestedBean/int", "nestedBean/strings", -1); assertDocumentOrder(context, "nestedBean/int", "object/int", -1); } public void testSetPropertyValue() { // Simple property assertXPathSetValue(context, "int", new Integer(2)); // Simple property with conversion from string assertXPathSetValue(context, "int", "3", new Integer(3)); // Simple property with conversion from array assertXPathSetValue(context, "int", new int[] { 4 }, new Integer(4)); // Attribute (which is the same as a child for beans assertXPathSetValue(context, "@int", new Integer(10)); } public void testSetCollectionElement() { // Collection element assertXPathSetValue(context, "integers[2]", new Integer(5)); // Collection element with conversion assertXPathSetValue( context, "integers[2]", new int[] { 6 }, new Integer(6)); } public void testSetContextDependentNode() { // Find node without using SimplePathInterpreter assertXPathSetValue( context, "integers[position() = 1]", new Integer(8)); // Find node without using SimplePathInterpreter and set its property assertXPathSetValue( context, "beans[name = 'Name 1']/int", new Integer(9)); } public void testSetNonPrimitiveValue() { // First, let's see if we can set a collection element to null assertXPathSetValue(context, "beans[2]", null); // Now, assign it a whole bean context.setValue("beans[2]", new NestedTestBean("Name 9")); assertEquals( "Modified <" + "beans[2]/name" + ">", "Name 9", context.getValue("beans[2]/name")); } public void testCreatePath() { context.setValue("nestedBean", null); // Calls factory.createObject(..., TestBean, "nestedBean") assertXPathCreatePath( context, "/nestedBean/int", new Integer(1), "/nestedBean/int"); boolean ex = false; try { assertXPathCreatePath( context, "/nestedBean/beans[last() + 1]", new Integer(1), "/nestedBean/beans[last() + 1]"); } catch (Exception e) { ex = true; } assertTrue("Exception thrown on invalid path for creation", ex); } public void testCreatePathAndSetValue() { context.setValue("nestedBean", null); // Calls factory.createObject(..., TestBean, "nestedBean") assertXPathCreatePathAndSetValue( context, "/nestedBean/int", new Integer(2), "/nestedBean/int"); } public void testCreatePathExpandNewCollection() { context.setValue("beans", null); // Calls factory.createObject(..., testBean, "beans", 2), // then factory.createObject(..., testBean, "beans", 2) assertXPathCreatePath( context, "/beans[2]/int", new Integer(1), "/beans[2]/int"); } public void testCreatePathAndSetValueExpandNewCollection() { context.setValue("beans", null); // Calls factory.createObject(..., testBean, "beans", 2), // then factory.createObject(..., testBean, "beans", 2) assertXPathCreatePathAndSetValue( context, "/beans[2]/int", new Integer(2), "/beans[2]/int"); } public void testCreatePathExpandExistingCollection() { // Calls factory.createObject(..., TestBean, "integers", 5) // to expand collection assertXPathCreatePathAndSetValue( context, "/integers[5]", new Integer(3), "/integers[5]"); } public void testCreatePathExpandExistingCollectionAndSetProperty() { // Another, but the collection already exists assertXPathCreatePath( context, "/beans[3]/int", new Integer(1), "/beans[3]/int"); } public void testCreatePathAndSetValueExpandExistingCollection() { // Another, but the collection already exists assertXPathCreatePathAndSetValue( context, "/beans[3]/int", new Integer(2), "/beans[3]/int"); } public void testCreatePathCreateBeanExpandCollection() { context.setValue("nestedBean", null); // Calls factory.createObject(..., TestBean, "nestedBean") // Calls factory.createObject(..., nestedBean, "strings", 2) assertXPathCreatePath( context, "/nestedBean/strings[2]", "String 2", "/nestedBean/strings[2]"); } public void testCreatePathAndSetValueCreateBeanExpandCollection() { context.setValue("nestedBean", null); // Calls factory.createObject(..., TestBean, "nestedBean") // Calls factory.createObject(..., nestedBean, "strings", 2) assertXPathCreatePathAndSetValue( context, "/nestedBean/strings[2]", "Test", "/nestedBean/strings[2]"); } public void testRemovePathPropertyValue() { // Remove property value context.removePath("nestedBean/int"); assertEquals( "Remove property value", new Integer(0), context.getValue("nestedBean/int")); } public void testRemovePathArrayElement() { // Assigns a new array to the property context.removePath("nestedBean/strings[1]"); assertEquals( "Remove array element", "String 2", context.getValue("nestedBean/strings[1]")); } public void testRemoveAllArrayElements() { context.removeAll("nestedBean/strings"); assertXPathValueIterator( context, "nestedBean/strings", list()); } public void testRemoveAllListElements() { context.removeAll("list"); assertXPathValueIterator( context, "list", this instanceof DynaBeanModelTest ? list(null, null, null) : list()); } public void testRemoveAllMapEntries() { context.removeAll("map/*"); assertXPathValue( context, "map", Collections.EMPTY_MAP); } public void testRemovePathBeanValue() { context.removePath("nestedBean"); assertEquals( "Remove collection element", null, context.getValue("nestedBean")); } public void testRelativeContextRelativePath() { JXPathContext relative = context.getRelativeContext(context.getPointer("nestedBean")); assertXPathValueAndPointer(relative, "int", new Integer(1), "/nestedBean/int"); } public void testRelativeContextAbsolutePath() { JXPathContext relative = context.getRelativeContext(context.getPointer("nestedBean")); assertXPathValueAndPointer(relative, "/integers[2]", new Integer(2), "/integers[2]"); } public void testRelativeContextParent() { JXPathContext relative = context.getRelativeContext(context.getPointer("nestedBean")); assertXPathValueAndPointer(relative, "../integers[2]", new Integer(2), "/integers[2]"); } public void testRelativeContextInheritance() { context.setFunctions(new ClassFunctions(TestFunctions.class, "test")); JXPathContext relative = context.getRelativeContext(context.getPointer("nestedBean")); assertXPathValue(relative, "test:countPointers(strings)", new Integer(3)); } }