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;
19  
20  import org.apache.commons.jxpath.AbstractFactory;
21  import org.apache.commons.jxpath.JXPathAbstractFactoryException;
22  import org.apache.commons.jxpath.JXPathContext;
23  import org.apache.commons.jxpath.JXPathException;
24  import org.apache.commons.jxpath.JXPathIntrospector;
25  import org.apache.commons.jxpath.JXPathInvalidAccessException;
26  import org.apache.commons.jxpath.Variables;
27  import org.apache.commons.jxpath.ri.QName;
28  import org.apache.commons.jxpath.ri.compiler.NodeTest;
29  import org.apache.commons.jxpath.ri.model.beans.NullPointer;
30  import org.apache.commons.jxpath.util.ValueUtils;
31  
32  /**
33   * Pointer to a context variable.
34   */
35  public class VariablePointer extends NodePointer {
36  
37      private static final long serialVersionUID = -454731297397189293L;
38  
39      /**
40       * Variables.
41       */
42      private Variables variables;
43  
44      /**
45       * Qualified name.
46       */
47      private final QName qName;
48  
49      /**
50       * Value pointer.
51       */
52      private NodePointer valuePointer;
53  
54      /**
55       * Implements {@link NodePointer#isActual()}.
56       */
57      private boolean actual;
58  
59      /**
60       * Constructs a new (non-actual) VariablePointer.
61       *
62       * @param qName variable name
63       */
64      public VariablePointer(final QName qName) {
65          super(null);
66          this.qName = qName;
67          actual = false;
68      }
69  
70      /**
71       * Constructs a new VariablePointer.
72       *
73       * @param variables Variables instance
74       * @param qName      variable name
75       */
76      public VariablePointer(final Variables variables, final QName qName) {
77          super(null);
78          this.variables = variables;
79          this.qName = qName;
80          actual = true;
81      }
82  
83      @Override
84      public String asPath() {
85          final StringBuilder buffer = new StringBuilder();
86          buffer.append('$');
87          buffer.append(qName);
88          if (!actual) {
89              if (index != WHOLE_COLLECTION) {
90                  buffer.append('[').append(index + 1).append(']');
91              }
92          } else if (index != WHOLE_COLLECTION && (getNode() == null || isCollection())) {
93              buffer.append('[').append(index + 1).append(']');
94          }
95          return buffer.toString();
96      }
97  
98      @Override
99      public NodeIterator attributeIterator(final QName qName) {
100         return getValuePointer().attributeIterator(qName);
101     }
102 
103     @Override
104     public NodeIterator childIterator(final NodeTest test, final boolean reverse, final NodePointer startWith) {
105         return getValuePointer().childIterator(test, reverse, startWith);
106     }
107 
108     @Override
109     public int compareChildNodePointers(final NodePointer pointer1, final NodePointer pointer2) {
110         return pointer1.getIndex() - pointer2.getIndex();
111     }
112 
113     @Override
114     public NodePointer createChild(final JXPathContext context, final QName qName, final int index) {
115         final Object collection = createCollection(context, index);
116         if (!isActual() || index != 0 && index != WHOLE_COLLECTION) {
117             final AbstractFactory factory = getAbstractFactory(context);
118             final boolean success = factory.createObject(context, this, collection, getName().toString(), index);
119             if (!success) {
120                 throw new JXPathAbstractFactoryException("Factory could not create object path: " + asPath());
121             }
122             final NodePointer cln = (NodePointer) clone();
123             cln.setIndex(index);
124             return cln;
125         }
126         return this;
127     }
128 
129     @Override
130     public NodePointer createChild(final JXPathContext context, final QName qName, final int index, final Object value) {
131         final Object collection = createCollection(context, index);
132         ValueUtils.setValue(collection, index, value);
133         final NodePointer cl = (NodePointer) clone();
134         cl.setIndex(index);
135         return cl;
136     }
137 
138     /**
139      * Create a collection.
140      *
141      * @param context JXPathContext
142      * @param index   collection index
143      * @return Object
144      */
145     private Object createCollection(final JXPathContext context, int index) {
146         createPath(context);
147         Object collection = getBaseValue();
148         if (collection == null) {
149             throw new JXPathAbstractFactoryException("Factory did not assign a collection to variable '" + qName + "' for path: " + asPath());
150         }
151         if (index == WHOLE_COLLECTION) {
152             index = 0;
153         } else if (index < 0) {
154             throw new JXPathInvalidAccessException("Index is less than 1: " + asPath());
155         }
156         if (index >= getLength()) {
157             collection = ValueUtils.expandCollection(collection, index + 1);
158             variables.declareVariable(qName.toString(), collection);
159         }
160         return collection;
161     }
162 
163     @Override
164     public NodePointer createPath(final JXPathContext context) {
165         if (!actual) {
166             final AbstractFactory factory = getAbstractFactory(context);
167             if (!factory.declareVariable(context, qName.toString())) {
168                 throw new JXPathAbstractFactoryException("Factory cannot define variable '" + qName + "' for path: " + asPath());
169             }
170             findVariables(context);
171             // Assert: actual == true
172         }
173         return this;
174     }
175 
176     @Override
177     public NodePointer createPath(final JXPathContext context, final Object value) {
178         if (actual) {
179             setValue(value);
180             return this;
181         }
182         final NodePointer ptr = createPath(context);
183         ptr.setValue(value);
184         return ptr;
185     }
186 
187     @Override
188     public boolean equals(final Object object) {
189         if (object == this) {
190             return true;
191         }
192         if (!(object instanceof VariablePointer)) {
193             return false;
194         }
195         final VariablePointer other = (VariablePointer) object;
196         return variables == other.variables && qName.equals(other.qName) && index == other.index;
197     }
198 
199     /**
200      * Assimilate the Variables instance associated with the specified context.
201      *
202      * @param context JXPathContext to search
203      */
204     protected void findVariables(final JXPathContext context) {
205         valuePointer = null;
206         JXPathContext varCtx = context;
207         while (varCtx != null) {
208             variables = varCtx.getVariables();
209             if (variables.isDeclaredVariable(qName.toString())) {
210                 actual = true;
211                 break;
212             }
213             varCtx = varCtx.getParentContext();
214             variables = null;
215         }
216     }
217 
218     @Override
219     public Object getBaseValue() {
220         if (!actual) {
221             throw new JXPathException("Undefined variable: " + qName);
222         }
223         return variables.getVariable(qName.toString());
224     }
225 
226     @Override
227     public Object getImmediateNode() {
228         final Object value = getBaseValue();
229         return index == WHOLE_COLLECTION ? ValueUtils.getValue(value) : ValueUtils.getValue(value, index);
230     }
231 
232     @Override
233     public NodePointer getImmediateValuePointer() {
234         if (valuePointer == null) {
235             Object value;
236             if (!actual) {
237                 return new NullPointer(this, getName()) {
238 
239                     private static final long serialVersionUID = 1L;
240 
241                     @Override
242                     public Object getImmediateNode() {
243                         throw new JXPathException("Undefined variable: " + qName);
244                     }
245                 };
246             }
247             value = getImmediateNode();
248             valuePointer = newChildNodePointer(this, null, value);
249         }
250         return valuePointer;
251     }
252 
253     @Override
254     public int getLength() {
255         if (actual) {
256             final Object value = getBaseValue();
257             return value == null ? 1 : ValueUtils.getLength(value);
258         }
259         return 0;
260     }
261 
262     @Override
263     public QName getName() {
264         return qName;
265     }
266 
267     @Override
268     public int hashCode() {
269         return (actual ? System.identityHashCode(variables) : 0) + qName.hashCode() + index;
270     }
271 
272     @Override
273     public boolean isActual() {
274         return actual;
275     }
276 
277     @Override
278     public boolean isCollection() {
279         final Object value = getBaseValue();
280         return value != null && ValueUtils.isCollection(value);
281     }
282 
283     @Override
284     public boolean isContainer() {
285         return true;
286     }
287 
288     @Override
289     public boolean isLeaf() {
290         final Object value = getNode();
291         return value == null || JXPathIntrospector.getBeanInfo(value.getClass()).isAtomic();
292     }
293 
294     @Override
295     public NodeIterator namespaceIterator() {
296         return getValuePointer().namespaceIterator();
297     }
298 
299     @Override
300     public NodePointer namespacePointer(final String name) {
301         return getValuePointer().namespacePointer(name);
302     }
303 
304     @Override
305     public void remove() {
306         if (actual) {
307             if (index == WHOLE_COLLECTION) {
308                 variables.undeclareVariable(qName.toString());
309             } else {
310                 if (index < 0) {
311                     throw new JXPathInvalidAccessException("Index is less than 1: " + asPath());
312                 }
313                 Object collection = getBaseValue();
314                 if (collection != null && index < getLength()) {
315                     collection = ValueUtils.remove(collection, index);
316                     variables.declareVariable(qName.toString(), collection);
317                 }
318             }
319         }
320     }
321 
322     @Override
323     public void setIndex(final int index) {
324         super.setIndex(index);
325         valuePointer = null;
326     }
327 
328     @Override
329     public void setValue(final Object value) {
330         if (!actual) {
331             throw new JXPathException("Cannot set undefined variable: " + qName);
332         }
333         valuePointer = null;
334         if (index != WHOLE_COLLECTION) {
335             final Object collection = getBaseValue();
336             ValueUtils.setValue(collection, index, value);
337         } else {
338             variables.declareVariable(qName.toString(), value);
339         }
340     }
341 
342     @Override
343     public boolean testNode(final NodeTest nodeTest) {
344         return getValuePointer().testNode(nodeTest);
345     }
346 }