    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}