    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    }