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
018package org.apache.commons.jxpath.ri.model;
019
020import org.apache.commons.jxpath.AbstractFactory;
021import org.apache.commons.jxpath.JXPathAbstractFactoryException;
022import org.apache.commons.jxpath.JXPathContext;
023import org.apache.commons.jxpath.JXPathException;
024import org.apache.commons.jxpath.JXPathIntrospector;
025import org.apache.commons.jxpath.JXPathInvalidAccessException;
026import org.apache.commons.jxpath.Variables;
027import org.apache.commons.jxpath.ri.QName;
028import org.apache.commons.jxpath.ri.compiler.NodeTest;
029import org.apache.commons.jxpath.ri.model.beans.NullPointer;
030import org.apache.commons.jxpath.util.ValueUtils;
031
032/**
033 * Pointer to a context variable.
034 */
035public class VariablePointer extends NodePointer {
036
037    private static final long serialVersionUID = -454731297397189293L;
038
039    /**
040     * Variables.
041     */
042    private Variables variables;
043
044    /**
045     * Qualified name.
046     */
047    private final QName qName;
048
049    /**
050     * Value pointer.
051     */
052    private NodePointer valuePointer;
053
054    /**
055     * Implements {@link NodePointer#isActual()}.
056     */
057    private boolean actual;
058
059    /**
060     * Constructs a new (non-actual) VariablePointer.
061     *
062     * @param qName variable name
063     */
064    public VariablePointer(final QName qName) {
065        super(null);
066        this.qName = qName;
067        actual = false;
068    }
069
070    /**
071     * Constructs a new VariablePointer.
072     *
073     * @param variables Variables instance
074     * @param qName      variable name
075     */
076    public VariablePointer(final Variables variables, final QName qName) {
077        super(null);
078        this.variables = variables;
079        this.qName = qName;
080        actual = true;
081    }
082
083    @Override
084    public String asPath() {
085        final StringBuilder buffer = new StringBuilder();
086        buffer.append('$');
087        buffer.append(qName);
088        if (!actual) {
089            if (index != WHOLE_COLLECTION) {
090                buffer.append('[').append(index + 1).append(']');
091            }
092        } else if (index != WHOLE_COLLECTION && (getNode() == null || isCollection())) {
093            buffer.append('[').append(index + 1).append(']');
094        }
095        return buffer.toString();
096    }
097
098    @Override
099    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}