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