    1   /*
    2    * Copyright 1999-2004 The Apache Software Foundation
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  package org.apache.commons.jxpath.ri.model;
    17
    18  import org.apache.commons.jxpath.AbstractFactory;
    19  import org.apache.commons.jxpath.JXPathContext;
    20  import org.apache.commons.jxpath.JXPathException;
    21  import org.apache.commons.jxpath.JXPathIntrospector;
    22  import org.apache.commons.jxpath.Variables;
    23  import org.apache.commons.jxpath.ri.QName;
    24  import org.apache.commons.jxpath.ri.compiler.NodeTest;
    25  import org.apache.commons.jxpath.ri.model.beans.NullPointer;
    26  import org.apache.commons.jxpath.util.ValueUtils;
    27
    28  /***
    29   * Pointer to a context variable.
    30   *
    31   * @author Dmitri Plotnikov
    32   * @version $Revision: 1.18 $ $Date: 2004/04/04 22:06:36 $
    33   */
    34  public class VariablePointer extends NodePointer {
    35      private Variables variables;
    36      private QName name;
    37      private NodePointer valuePointer;
    38      private boolean actual;
    39
    40      public VariablePointer(Variables variables, QName name) {
    41          super(null);
    42          this.variables = variables;
    43          this.name = name;
    44          actual = true;
    45      }
    46
    47      public VariablePointer(QName name) {
    48          super(null);
    49          this.name = name;
    50          actual = false;
    51      }
    52
    53      public boolean isContainer() {
    54          return true;
    55      }
    56
    57      public QName getName() {
    58          return name;
    59      }
    60
    61      public Object getBaseValue() {
    62          if (!actual) {
    63              throw new JXPathException("Undefined variable: " + name);
    64          }
    65          return variables.getVariable(name.toString());
    66      }
    67
    68      public boolean isLeaf() {
    69          Object value = getNode();
    70          return value == null
    71              || JXPathIntrospector.getBeanInfo(value.getClass()).isAtomic();
    72      }
    73
    74      public boolean isCollection() {
    75          Object value = getBaseValue();
    76          return value != null && ValueUtils.isCollection(value);
    77      }
    78
    79      public Object getImmediateNode() {
    80          Object value = getBaseValue();
    81          if (index != WHOLE_COLLECTION) {
    82              return ValueUtils.getValue(value, index);
    83          }
    84          else {
    85              return ValueUtils.getValue(value);
    86          }
    87      }
    88
    89      public void setValue(Object value) {
    90          if (!actual) {
    91              throw new JXPathException("Cannot set undefined variable: " + name);
    92          }
    93          valuePointer = null;
    94          if (index != WHOLE_COLLECTION) {
    95              Object collection = getBaseValue();
    96              ValueUtils.setValue(collection, index, value);
    97          }
    98          else {
    99              variables.declareVariable(name.toString(), value);
    100         }
    101     }
    102
    103     public boolean isActual() {
    104         return actual;
    105     }
    106
    107     public void setIndex(int index) {
    108         super.setIndex(index);
    109         valuePointer = null;
    110     }
    111
    112     public NodePointer getImmediateValuePointer() {
    113         if (valuePointer == null) {
    114             Object value = null;
    115             if (actual) {
    116                 value = getImmediateNode();
    117                 valuePointer =
    118                     NodePointer.newChildNodePointer(this, null, value);
    119             }
    120             else {
    121                 return new NullPointer(this, getName()) {
    122                     public Object getImmediateNode() {
    123                         throw new JXPathException(
    124                             "Undefined variable: " + name);
    125                     }
    126                 };
    127             }
    128         }
    129         return valuePointer;
    130     }
    131
    132     public int getLength() {
    133         if (actual) {
    134             Object value = getBaseValue();
    135             if (value == null) {
    136                 return 1;
    137             }
    138             return ValueUtils.getLength(value);
    139         }
    140         return 0;
    141     }
    142
    143     public NodePointer createPath(JXPathContext context, Object value) {
    144         if (actual) {
    145             setValue(value);
    146             return this;
    147         }
    148         NodePointer ptr = createPath(context);
    149         ptr.setValue(value);
    150         return ptr;
    151     }
    152
    153     public NodePointer createPath(JXPathContext context) {
    154         if (!actual) {
    155             AbstractFactory factory = getAbstractFactory(context);
    156             if (!factory.declareVariable(context, name.toString())) {
    157                 throw new JXPathException(
    158                     "Factory cannot define variable '"
    159                         + name
    160                         + "' for path: "
    161                         + asPath());
    162             }
    163             findVariables(context);
    164             // Assert: actual == true
    165         }
    166         return this;
    167     }
    168
    169     public NodePointer createChild(
    170         JXPathContext context,
    171         QName name,
    172         int index)
    173     {
    174         Object collection = createCollection(context, index);
    175         if (!isActual() || (index != 0 && index != WHOLE_COLLECTION)) {
    176             AbstractFactory factory = getAbstractFactory(context);
    177             boolean success =
    178                 factory.createObject(
    179                     context,
    180                     this,
    181                     collection,
    182                     getName().toString(),
    183                     index);
    184             if (!success) {
    185                 throw new JXPathException(
    186                     "Factory could not create object path: " + asPath());
    187             }
    188             NodePointer cln = (NodePointer) clone();
    189             cln.setIndex(index);
    190             return cln;
    191         }
    192         return this;
    193     }
    194
    195     public NodePointer createChild(
    196             JXPathContext context,
    197             QName name,
    198             int index,
    199             Object value)
    200     {
    201         Object collection = createCollection(context, index);
    202         ValueUtils.setValue(collection, index, value);
    203         NodePointer cl = (NodePointer) clone();
    204         cl.setIndex(index);
    205         return cl;
    206     }
    207
    208     private Object createCollection(JXPathContext context, int index) {
    209         createPath(context);
    210
    211         Object collection = getBaseValue();
    212         if (collection == null) {
    213             throw new JXPathException(
    214                 "Factory did not assign a collection to variable '"
    215                     + name
    216                     + "' for path: "
    217                     + asPath());
    218         }
    219
    220         if (index == WHOLE_COLLECTION) {
    221             index = 0;
    222         }
    223         else if (index < 0) {
    224             throw new JXPathException("Index is less than 1: " + asPath());
    225         }
    226
    227         if (index >= getLength()) {
    228             collection = ValueUtils.expandCollection(collection, index + 1);
    229             variables.declareVariable(name.toString(), collection);
    230         }
    231
    232         return collection;
    233     }
    234
    235     public void remove() {
    236         if (actual) {
    237             if (index == WHOLE_COLLECTION) {
    238                 variables.undeclareVariable(name.toString());
    239             }
    240             else {
    241                 if (index < 0) {
    242                     throw new JXPathException(
    243                         "Index is less than 1: " + asPath());
    244                 }
    245
    246                 Object collection = getBaseValue();
    247                 if (collection != null && index < getLength()) {
    248                     collection = ValueUtils.remove(collection, index);
    249                     variables.declareVariable(name.toString(), collection);
    250                 }
    251             }
    252         }
    253     }
    254
    255     protected void findVariables(JXPathContext context) {
    256         valuePointer = null;
    257         JXPathContext varCtx = context;
    258         while (varCtx != null) {
    259             variables = varCtx.getVariables();
    260             if (variables.isDeclaredVariable(name.toString())) {
    261                 actual = true;
    262                 break;
    263             }
    264             varCtx = varCtx.getParentContext();
    265             variables = null;
    266         }
    267     }
    268
    269     public int hashCode() {
    270         return (actual ? System.identityHashCode(variables) : 0)
    271             + name.hashCode()
    272             + index;
    273     }
    274
    275     public boolean equals(Object object) {
    276         if (object == this) {
    277             return true;
    278         }
    279
    280         if (!(object instanceof VariablePointer)) {
    281             return false;
    282         }
    283
    284         VariablePointer other = (VariablePointer) object;
    285         return variables == other.variables
    286             && name.equals(other.name)
    287             && index == other.index;
    288     }
    289
    290     public String asPath() {
    291         StringBuffer buffer = new StringBuffer();
    292         buffer.append('$');
    293         buffer.append(name);
    294         if (!actual) {
    295             if (index != WHOLE_COLLECTION) {
    296                 buffer.append('[').append(index + 1).append(']');
    297             }
    298         }
    299         else if (
    300             index != WHOLE_COLLECTION
    301                 && (getNode() == null || isCollection())) {
    302             buffer.append('[').append(index + 1).append(']');
    303         }
    304         return buffer.toString();
    305     }
    306
    307     public NodeIterator childIterator(
    308         NodeTest test,
    309         boolean reverse,
    310         NodePointer startWith)
    311     {
    312         return getValuePointer().childIterator(test, reverse, startWith);
    313     }
    314
    315     public NodeIterator attributeIterator(QName name) {
    316         return getValuePointer().attributeIterator(name);
    317     }
    318
    319     public NodeIterator namespaceIterator() {
    320         return getValuePointer().namespaceIterator();
    321     }
    322
    323     public NodePointer namespacePointer(String name) {
    324         return getValuePointer().namespacePointer(name);
    325     }
    326
    327     public boolean testNode(NodeTest nodeTest) {
    328         return getValuePointer().testNode(nodeTest);
    329     }
    330
    331     private AbstractFactory getAbstractFactory(JXPathContext context) {
    332         AbstractFactory factory = context.getFactory();
    333         if (factory == null) {
    334             throw new JXPathException(
    335               "Factory is not set on the JXPathContext - cannot create path: "
    336               + asPath());
    337         }
    338         return factory;
    339     }
    340
    341     public int compareChildNodePointers(
    342         NodePointer pointer1,
    343         NodePointer pointer2)
    344     {
    345         return pointer1.getIndex() - pointer2.getIndex();
    346     }
    347 }