dmitri 2004/01/22 17:10:21
Modified: jxpath/src/java/org/apache/commons/jxpath/ri/model/dynamic
DynamicPropertyIterator.java DynamicPointer.java
jxpath/src/java/org/apache/commons/jxpath/ri/axes
SimplePathInterpreter.java
jxpath/src/test/org/apache/commons/jxpath/ri/model/dynamic
DynamicPropertiesModelTest.java
jxpath/src/java/org/apache/commons/jxpath/ri/compiler
Path.java
jxpath/src/java/org/apache/commons/jxpath/ri/model/beans
PropertyOwnerPointer.java NullPropertyPointer.java
Log:
Fixed an issue with searches in graphs containing DynamicPropertyHandlers
Revision Changes Path
1.4 +2 -9
jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/model/dynamic/DynamicPropertyIterator.java
Index: DynamicPropertyIterator.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/model/dynamic/DynamicPropertyIterator.java,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- DynamicPropertyIterator.java 9 Oct 2003 21:31:41 -0000 1.3
+++ DynamicPropertyIterator.java 23 Jan 2004 01:10:20 -0000 1.4
@@ -58,9 +58,7 @@
import org.apache.commons.jxpath.ri.model.beans.PropertyOwnerPointer;
/**
- * <code>DynamicPropertyIterator</code> is different from a regular
- * <code>PropertyIterator</code> in that given a property name it
- * will always find that property (albeit with a null value).
+ * @deprecated - no longer needed, as it is identical to PropertyIterator.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Dmitri Plotnikov</a>
* @version $Id$
@@ -75,9 +73,4 @@
{
super(pointer, name, reverse, startWith);
}
-
- protected void prepareForIndividualProperty(String name) {
- ((DynamicPropertyPointer) getPropertyPointer()).setPropertyName(name);
- super.prepareForIndividualProperty(name);
- }
}
1.5 +11 -6
jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/model/dynamic/DynamicPointer.java
Index: DynamicPointer.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/model/dynamic/DynamicPointer.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- DynamicPointer.java 9 Oct 2003 21:31:41 -0000 1.4
+++ DynamicPointer.java 23 Jan 2004 01:10:20 -0000 1.5
@@ -68,6 +68,7 @@
import org.apache.commons.jxpath.ri.QName;
import org.apache.commons.jxpath.ri.model.NodeIterator;
import org.apache.commons.jxpath.ri.model.NodePointer;
+import org.apache.commons.jxpath.ri.model.beans.PropertyIterator;
import org.apache.commons.jxpath.ri.model.beans.PropertyOwnerPointer;
import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
@@ -110,7 +111,7 @@
public NodeIterator createNodeIterator(
String property, boolean reverse, NodePointer startWith)
{
- return new DynamicPropertyIterator(this, property, reverse, startWith);
+ return new PropertyIterator(this, property, reverse, startWith);
}
public NodeIterator attributeIterator(QName name) {
@@ -120,7 +121,11 @@
public QName getName() {
return name;
}
-
+
+ public boolean isDynamicPropertyDeclarationSupported() {
+ return true;
+ }
+
/**
* Returns the DP object iself.
*/
1.13 +1 -1
jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/axes/SimplePathInterpreter.java
Index: SimplePathInterpreter.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/axes/SimplePathInterpreter.java,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -r1.12 -r1.13
--- SimplePathInterpreter.java 9 Oct 2003 21:31:39 -0000 1.12
+++ SimplePathInterpreter.java 23 Jan 2004 01:10:20 -0000 1.13
@@ -787,7 +787,7 @@
* a) represents the requested path and
* b) can be used for creation of missing nodes in the path.
*/
- private static NodePointer createNullPointer(
+ public static NodePointer createNullPointer(
EvalContext context, NodePointer parent, Step[] steps,
int currentStep)
{
1.7 +49 -5
jakarta-commons/jxpath/src/test/org/apache/commons/jxpath/ri/model/dynamic/DynamicPropertiesModelTest.java
Index: DynamicPropertiesModelTest.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/jxpath/src/test/org/apache/commons/jxpath/ri/model/dynamic/DynamicPropertiesModelTest.java,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -r1.6 -r1.7
--- DynamicPropertiesModelTest.java 9 Oct 2003 21:31:44 -0000 1.6
+++ DynamicPropertiesModelTest.java 23 Jan 2004 01:10:20 -0000 1.7
@@ -184,8 +184,18 @@
assertXPathSetValue(context, "map/Key1[1]", new Integer(9));
}
+ /**
+ * The key does not exist, but the assignment should succeed anyway,
+ * because you should always be able to store anything in a Map.
+ */
public void testSetNewKey() {
+ // Using a "simple" path
assertXPathSetValue(context, "map/Key4", new Integer(7));
+
+ // Using a "non-simple" path
+ assertXPathPointerLenient(context, "//map/Key5", "/map/Key5");
+
+ assertXPathSetValue(context, "//map/Key5", new Integer(8));
}
public void testCreatePath() {
@@ -369,6 +379,40 @@
assertXPathValueIterator(
context,
"/map/[EMAIL PROTECTED]'fruit']",
- list("apple", "banana"));
+ list("apple", "banana"));
+ }
+
+ public void testMapOfMaps() {
+ TestBean bean = (TestBean) context.getContextBean();
+
+ Map fruit = new HashMap();
+ fruit.put("apple", "green");
+ fruit.put("orange", "red");
+
+ Map meat = new HashMap();
+ meat.put("pork", "pig");
+ meat.put("beef", "cow");
+
+ bean.getMap().put("fruit", fruit);
+ bean.getMap().put("meat", meat);
+
+ assertXPathPointer(
+ context,
+ "//beef",
+ "/[EMAIL PROTECTED]'meat'[EMAIL PROTECTED]'beef']");
+
+ assertXPathPointer(
+ context,
+ "map//apple",
+ "/[EMAIL PROTECTED]'fruit'[EMAIL PROTECTED]'apple']");
+
+ // Ambiguous search - will return nothing
+ assertXPathPointerLenient(context, "map//banana", "null()");
+
+ // Unambiguous, even though the particular key is missing
+ assertXPathPointerLenient(
+ context,
+ "//fruit/pear",
+ "/[EMAIL PROTECTED]'fruit']/pear");
}
}
1.11 +122 -71
jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/compiler/Path.java
Index: Path.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/compiler/Path.java,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -r1.10 -r1.11
--- Path.java 9 Oct 2003 21:31:39 -0000 1.10
+++ Path.java 23 Jan 2004 01:10:20 -0000 1.11
@@ -68,6 +68,7 @@
import org.apache.commons.jxpath.ri.axes.AttributeContext;
import org.apache.commons.jxpath.ri.axes.ChildContext;
import org.apache.commons.jxpath.ri.axes.DescendantContext;
+import org.apache.commons.jxpath.ri.axes.InitialContext;
import org.apache.commons.jxpath.ri.axes.NamespaceContext;
import org.apache.commons.jxpath.ri.axes.ParentContext;
import org.apache.commons.jxpath.ri.axes.PrecedingOrFollowingContext;
@@ -117,28 +118,7 @@
basic = true;
Step[] steps = getSteps();
for (int i = 0; i < steps.length; i++) {
- boolean accepted = false;
- if (steps[i].getAxis() == Compiler.AXIS_SELF
- && (steps[i].getNodeTest() instanceof NodeTypeTest)
- && ((NodeTypeTest) steps[i].getNodeTest()).getNodeType()
- == Compiler.NODE_TYPE_NODE) {
- accepted = true;
- }
- else if (
- (steps[i].getAxis() == Compiler.AXIS_CHILD
- || steps[i].getAxis() == Compiler.AXIS_ATTRIBUTE)
- && (steps[i].getNodeTest() instanceof NodeNameTest)
- && !((NodeNameTest) steps[i].getNodeTest())
- .getNodeName()
- .getName()
- .equals(
- "*")) {
- accepted = true;
- }
- if (accepted) {
- accepted = areBasicPredicates(steps[i].getPredicates());
- }
- if (!accepted) {
+ if (!isSimpleStep(steps[i])){
basic = false;
break;
}
@@ -147,6 +127,39 @@
return basic;
}
+ /**
+ * A Step is "simple" if it takes one of these forms: ".", "/foo",
+ * "@bar", "/foo[3]". If there are predicates, they should be
+ * context independent for the step to still be considered simple.
+ */
+ protected boolean isSimpleStep(Step step) {
+ if (step.getAxis() == Compiler.AXIS_SELF) {
+ NodeTest nodeTest = step.getNodeTest();
+ if (!(nodeTest instanceof NodeTypeTest)) {
+ return false;
+ }
+ int nodeType = ((NodeTypeTest) nodeTest).getNodeType();
+ if (nodeType != Compiler.NODE_TYPE_NODE) {
+ return false;
+ }
+ return areBasicPredicates(step.getPredicates());
+ }
+ else if (step.getAxis() == Compiler.AXIS_CHILD
+ || step.getAxis() == Compiler.AXIS_ATTRIBUTE) {
+ NodeTest nodeTest = step.getNodeTest();
+ if (!(nodeTest instanceof NodeNameTest)){
+ return false;
+ }
+
+ String name = ((NodeNameTest) nodeTest).getNodeName().getName();
+ if (name.equals("*")) {
+ return false;
+ }
+ return areBasicPredicates(step.getPredicates());
+ }
+ return false;
+ }
+
protected boolean areBasicPredicates(Expression predicates[]) {
if (predicates != null && predicates.length != 0) {
boolean firstIndex = true;
@@ -193,22 +206,54 @@
}
}
+ /**
+ * The idea here is to return a NullPointer rather than null if that's at
+ * all possible. Take for example this path: "//map/key". Let's say, "map"
+ * is an existing node, but "key" is not there. We will create a
+ * NullPointer that can be used to set/create the "key" property.
+ * <p>
+ * However, a path like "//key" would still produce null, because we have
+ * no way of knowing where "key" would be if it existed.
+ * </p>
+ * <p>
+ * To accomplish this, we first try the path itself. If it does not find
+ * anything, we chop off last step of the path, as long as it is a simple
+ * one like child:: or attribute:: and try to evaluate the truncated path.
+ * If it finds exactly one node - create a NullPointer and return. If it
+ * fails, chop off another step and repeat. If it finds more than one
+ * location - return null.
+ * </p>
+ */
private Pointer searchForPath(EvalContext context) {
- for (int i = 0; i < steps.length; i++) {
- context =
- createContextForStep(
- context,
- steps[i].getAxis(),
- steps[i].getNodeTest());
- Expression predicates[] = steps[i].getPredicates();
- if (predicates != null) {
- for (int j = 0; j < predicates.length; j++) {
- context = new PredicateContext(context, predicates[j]);
+ EvalContext ctx = buildContextChain(context, steps.length, true);
+ Pointer pointer = ctx.getSingleNodePointer();
+
+ if (pointer != null) {
+ return pointer;
+ }
+
+ for (int i = steps.length; --i > 0;) {
+ if (!isSimpleStep(steps[i])) {
+ return null;
+ }
+ ctx = buildContextChain(context, i, true);
+ if (ctx.hasNext()) {
+ Pointer partial = (Pointer) ctx.next();
+ if (ctx.hasNext()) {
+ // If we find another location - the search is
+ // ambiguous, so we report failure
+ return null;
+ }
+ if (partial instanceof NodePointer) {
+ return SimplePathInterpreter.createNullPointer(
+ context,
+ (NodePointer) partial,
+ steps,
+ i);
}
}
}
-
- return context.getSingleNodePointer();
+ return null;
}
/**
@@ -216,11 +261,21 @@
* that contains all nodes matching the path.
*/
protected EvalContext evalSteps(EvalContext context) {
+ return buildContextChain(context, steps.length, false);
+ }
+
+ private EvalContext buildContextChain(
+ EvalContext context,
+ int stepCount,
+ boolean createInitialContext)
+ {
+ if (createInitialContext) {
+ context = new InitialContext(context);
+ }
if (steps.length == 0) {
return context;
}
-
- for (int i = 0; i < steps.length; i++) {
+ for (int i = 0; i < stepCount; i++) {
context =
createContextForStep(
context,
@@ -233,10 +288,9 @@
}
}
}
-
return context;
}
-
+
/**
* Different axes are serviced by different contexts. This method
* allocates the right context for the supplied step.
@@ -247,35 +301,32 @@
NodeTest nodeTest)
{
switch (axis) {
- case Compiler.AXIS_ANCESTOR :
- return new AncestorContext(context, false, nodeTest);
- case Compiler.AXIS_ANCESTOR_OR_SELF :
- return new AncestorContext(context, true, nodeTest);
- case Compiler.AXIS_ATTRIBUTE :
- return new AttributeContext(context, nodeTest);
- case Compiler.AXIS_CHILD :
- return new ChildContext(context, nodeTest, false, false);
- case Compiler.AXIS_DESCENDANT :
- return new DescendantContext(context, false, nodeTest);
- case Compiler.AXIS_DESCENDANT_OR_SELF :
- return new DescendantContext(context, true, nodeTest);
- case Compiler.AXIS_FOLLOWING :
- return new PrecedingOrFollowingContext(
- context,
- nodeTest,
- false);
- case Compiler.AXIS_FOLLOWING_SIBLING :
- return new ChildContext(context, nodeTest, true, false);
- case Compiler.AXIS_NAMESPACE :
- return new NamespaceContext(context, nodeTest);
- case Compiler.AXIS_PARENT :
- return new ParentContext(context, nodeTest);
- case Compiler.AXIS_PRECEDING :
- return new PrecedingOrFollowingContext(context, nodeTest, true);
- case Compiler.AXIS_PRECEDING_SIBLING :
- return new ChildContext(context, nodeTest, true, true);
- case Compiler.AXIS_SELF :
- return new SelfContext(context, nodeTest);
+ case Compiler.AXIS_ANCESTOR :
+ return new AncestorContext(context, false, nodeTest);
+ case Compiler.AXIS_ANCESTOR_OR_SELF :
+ return new AncestorContext(context, true, nodeTest);
+ case Compiler.AXIS_ATTRIBUTE :
+ return new AttributeContext(context, nodeTest);
+ case Compiler.AXIS_CHILD :
+ return new ChildContext(context, nodeTest, false, false);
+ case Compiler.AXIS_DESCENDANT :
+ return new DescendantContext(context, false, nodeTest);
+ case Compiler.AXIS_DESCENDANT_OR_SELF :
+ return new DescendantContext(context, true, nodeTest);
+ case Compiler.AXIS_FOLLOWING :
+ return new PrecedingOrFollowingContext(context, nodeTest, false);
+ case Compiler.AXIS_FOLLOWING_SIBLING :
+ return new ChildContext(context, nodeTest, true, false);
+ case Compiler.AXIS_NAMESPACE :
+ return new NamespaceContext(context, nodeTest);
+ case Compiler.AXIS_PARENT :
+ return new ParentContext(context, nodeTest);
+ case Compiler.AXIS_PRECEDING :
+ return new PrecedingOrFollowingContext(context, nodeTest, true);
+ case Compiler.AXIS_PRECEDING_SIBLING :
+ return new ChildContext(context, nodeTest, true, true);
+ case Compiler.AXIS_SELF :
+ return new SelfContext(context, nodeTest);
}
return null; // Never happens
}
1.16 +13 -4
jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/model/beans/PropertyOwnerPointer.java
Index: PropertyOwnerPointer.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/model/beans/PropertyOwnerPointer.java,v
retrieving revision 1.15
retrieving revision 1.16
diff -u -r1.15 -r1.16
--- PropertyOwnerPointer.java 9 Oct 2003 21:31:40 -0000 1.15
+++ PropertyOwnerPointer.java 23 Jan 2004 01:10:21 -0000 1.16
@@ -200,6 +200,15 @@
}
public abstract PropertyPointer getPropertyPointer();
+
+ /**
+ * @return true if the property owner can set a property "does not exist".
+ * A good example is a Map. You can always assign a value to any
+ * key even if it has never been "declared".
+ */
+ public boolean isDynamicPropertyDeclarationSupported() {
+ return false;
+ }
public int compareChildNodePointers(
NodePointer pointer1,
1.15 +14 -4
jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/model/beans/NullPropertyPointer.java
Index: NullPropertyPointer.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/ri/model/beans/NullPropertyPointer.java,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -r1.14 -r1.15
--- NullPropertyPointer.java 9 Oct 2003 21:31:40 -0000 1.14
+++ NullPropertyPointer.java 23 Jan 2004 01:10:21 -0000 1.15
@@ -127,6 +127,16 @@
+ asPath()
+ ", the target object is null");
}
+ else if (parent instanceof PropertyOwnerPointer &&
+ ((PropertyOwnerPointer) parent).
+ isDynamicPropertyDeclarationSupported()){
+ // If the parent property owner can create
+ // a property automatically - let it do so
+ PropertyPointer propertyPointer =
+ ((PropertyOwnerPointer) parent).getPropertyPointer();
+ propertyPointer.setPropertyName(propertyName);
+ propertyPointer.setValue(value);
+ }
else {
throw new JXPathException(
"Cannot set property "
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]