View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jxpath.ri.model.beans;
19  
20  import java.util.Locale;
21  
22  import org.apache.commons.jxpath.JXPathInvalidAccessException;
23  import org.apache.commons.jxpath.ri.Compiler;
24  import org.apache.commons.jxpath.ri.QName;
25  import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
26  import org.apache.commons.jxpath.ri.compiler.NodeTest;
27  import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
28  import org.apache.commons.jxpath.ri.model.NodeIterator;
29  import org.apache.commons.jxpath.ri.model.NodePointer;
30  import org.apache.commons.jxpath.util.ValueUtils;
31  
32  /**
33   * A pointer describing a node that has properties, each of which could be a collection.
34   */
35  public abstract class PropertyOwnerPointer extends NodePointer {
36  
37      private static final long serialVersionUID = 1L;
38      private static final Object UNINITIALIZED = new Object();
39  
40      /**
41       * Supports {@link #getImmediateNode}.
42       */
43      private Object value = UNINITIALIZED;
44  
45      /**
46       * Constructs a new PropertyOwnerPointer.
47       *
48       * @param parent pointer
49       */
50      protected PropertyOwnerPointer(final NodePointer parent) {
51          super(parent);
52      }
53  
54      /**
55       * Constructs a new PropertyOwnerPointer.
56       *
57       * @param parent parent pointer
58       * @param locale Locale
59       */
60      protected PropertyOwnerPointer(final NodePointer parent, final Locale locale) {
61          super(parent, locale);
62      }
63  
64      @Override
65      public NodeIterator attributeIterator(final QName qName) {
66          return new BeanAttributeIterator(this, qName);
67      }
68  
69      @Override
70      public NodeIterator childIterator(final NodeTest test, final boolean reverse, final NodePointer startWith) {
71          if (test == null) {
72              return createNodeIterator(null, reverse, startWith);
73          }
74          if (test instanceof NodeNameTest) {
75              final NodeNameTest nodeNameTest = (NodeNameTest) test;
76              final QName testName = nodeNameTest.getNodeName();
77              if (isValidProperty(testName)) {
78                  return createNodeIterator(nodeNameTest.isWildcard() ? null : testName.toString(), reverse, startWith);
79              }
80              return null;
81          }
82          return test instanceof NodeTypeTest && ((NodeTypeTest) test).getNodeType() == Compiler.NODE_TYPE_NODE ? createNodeIterator(null, reverse, startWith)
83                  : null;
84      }
85  
86      @Override
87      public int compareChildNodePointers(final NodePointer pointer1, final NodePointer pointer2) {
88          final int r = pointer1.getName().toString().compareTo(pointer2.getName().toString());
89          return r == 0 ? pointer1.getIndex() - pointer2.getIndex() : r;
90      }
91  
92      /**
93       * Create a NodeIterator.
94       *
95       * @param property  property name
96       * @param reverse   whether to iterate in reverse
97       * @param startWith first pointer to return
98       * @return NodeIterator
99       */
100     public NodeIterator createNodeIterator(final String property, final boolean reverse, final NodePointer startWith) {
101         return new PropertyIterator(this, property, reverse, startWith);
102     }
103 
104     @Override
105     public Object getImmediateNode() {
106         if (value == UNINITIALIZED) {
107             value = index == WHOLE_COLLECTION ? ValueUtils.getValue(getBaseValue()) : ValueUtils.getValue(getBaseValue(), index);
108         }
109         return value;
110     }
111 
112     @Override
113     public abstract QName getName();
114 
115     /**
116      * Gets a PropertyPointer for this PropertyOwnerPointer.
117      *
118      * @return PropertyPointer
119      */
120     public abstract PropertyPointer getPropertyPointer();
121 
122     /**
123      * Tests whether dynamic property declaration is supported.
124      *
125      * @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
126      *         never been "declared".
127      */
128     public boolean isDynamicPropertyDeclarationSupported() {
129         return false;
130     }
131 
132     /**
133      * Tests whether {@code name} is a valid child name for this PropertyOwnerPointer.
134      *
135      * @param qName the QName to test
136      * @return {@code true} if {@code QName} is a valid property name.
137      * @since JXPath 1.3
138      */
139     public boolean isValidProperty(final QName qName) {
140         return isDefaultNamespace(qName.getPrefix());
141     }
142 
143     /**
144      * If this is a root node pointer, throws an exception; otherwise forwards the call to the parent node.
145      */
146     @Override
147     public void remove() {
148         this.value = null;
149         if (parent == null) {
150             throw new UnsupportedOperationException("Cannot remove an object that is not " + "some other object's property or a collection element");
151         }
152         parent.remove();
153     }
154 
155     @Override
156     public void setIndex(final int index) {
157         if (this.index != index) {
158             super.setIndex(index);
159             value = UNINITIALIZED;
160         }
161     }
162 
163     /**
164      * Throws an exception if you try to change the root element, otherwise forwards the call to the parent pointer.
165      *
166      * @param value to set
167      */
168     @Override
169     public void setValue(final Object value) {
170         this.value = value;
171         if (parent == null) {
172             throw new UnsupportedOperationException("Cannot replace the root object");
173         }
174         if (!parent.isContainer()) {
175             if (index == WHOLE_COLLECTION) {
176                 throw new UnsupportedOperationException("Cannot setValue of an object that is not " + "some other object's property");
177             }
178             throw new JXPathInvalidAccessException("The specified collection element does not exist: " + this);
179         }
180         parent.setValue(value);
181     }
182 }