001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.jxpath.ri.model;
018    
019    import org.apache.commons.jxpath.AbstractFactory;
020    import org.apache.commons.jxpath.JXPathAbstractFactoryException;
021    import org.apache.commons.jxpath.JXPathContext;
022    import org.apache.commons.jxpath.JXPathException;
023    import org.apache.commons.jxpath.JXPathIntrospector;
024    import org.apache.commons.jxpath.JXPathInvalidAccessException;
025    import org.apache.commons.jxpath.Variables;
026    import org.apache.commons.jxpath.ri.QName;
027    import org.apache.commons.jxpath.ri.compiler.NodeTest;
028    import org.apache.commons.jxpath.ri.model.beans.NullPointer;
029    import org.apache.commons.jxpath.util.ValueUtils;
030    
031    /**
032     * Pointer to a context variable.
033     *
034     * @author Dmitri Plotnikov
035     * @version $Revision: 652884 $ $Date: 2008-05-02 15:02:00 -0500 (Fri, 02 May 2008) $
036     */
037    public class VariablePointer extends NodePointer {
038        private Variables variables;
039        private QName name;
040        private NodePointer valuePointer;
041        private boolean actual;
042    
043        private static final long serialVersionUID = -454731297397189293L;
044    
045        /**
046         * Create a new VariablePointer.
047         * @param variables Variables instance
048         * @param name variable name
049         */
050        public VariablePointer(Variables variables, QName name) {
051            super(null);
052            this.variables = variables;
053            this.name = name;
054            actual = true;
055        }
056    
057        /**
058         * Create a new (non-actual) VariablePointer.
059         * @param name variable name
060         */
061        public VariablePointer(QName name) {
062            super(null);
063            this.name = name;
064            actual = false;
065        }
066    
067        public boolean isContainer() {
068            return true;
069        }
070    
071        public QName getName() {
072            return name;
073        }
074    
075        public Object getBaseValue() {
076            if (!actual) {
077                throw new JXPathException("Undefined variable: " + name);
078            }
079            return variables.getVariable(name.toString());
080        }
081    
082        public boolean isLeaf() {
083            Object value = getNode();
084            return value == null || JXPathIntrospector.getBeanInfo(value.getClass()).isAtomic();
085        }
086    
087        public boolean isCollection() {
088            Object value = getBaseValue();
089            return value != null && ValueUtils.isCollection(value);
090        }
091    
092        public Object getImmediateNode() {
093            Object value = getBaseValue();
094            return index == WHOLE_COLLECTION ? ValueUtils.getValue(value)
095                    : ValueUtils.getValue(value, index);
096        }
097    
098        public void setValue(Object value) {
099            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    }