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]

Reply via email to