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 */
017package org.apache.commons.jxpath.ri.model;
018
019import org.apache.commons.jxpath.AbstractFactory;
020import org.apache.commons.jxpath.JXPathAbstractFactoryException;
021import org.apache.commons.jxpath.JXPathContext;
022import org.apache.commons.jxpath.JXPathException;
023import org.apache.commons.jxpath.JXPathIntrospector;
024import org.apache.commons.jxpath.JXPathInvalidAccessException;
025import org.apache.commons.jxpath.Variables;
026import org.apache.commons.jxpath.ri.QName;
027import org.apache.commons.jxpath.ri.compiler.NodeTest;
028import org.apache.commons.jxpath.ri.model.beans.NullPointer;
029import 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 22:02:00 +0200 (Fr, 02 Mai 2008) $
036 */
037public 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}