    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.dynamic;
    17
    18  import java.util.Arrays;
    19  import java.util.Map;
    20
    21  import org.apache.commons.jxpath.AbstractFactory;
    22  import org.apache.commons.jxpath.DynamicPropertyHandler;
    23  import org.apache.commons.jxpath.JXPathContext;
    24  import org.apache.commons.jxpath.JXPathException;
    25  import org.apache.commons.jxpath.ri.model.NodePointer;
    26  import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
    27  import org.apache.commons.jxpath.util.ValueUtils;
    28
    29  /***
    30   * Pointer pointing to a property of an object with dynamic properties.
    31   *
    32   * @author Dmitri Plotnikov
    33   * @version $Revision: 1.8 $ $Date: 2004/04/04 22:06:36 $
    34   */
    35  public class DynamicPropertyPointer extends PropertyPointer {
    36      private DynamicPropertyHandler handler;
    37      private String name;
    38      private String[] names;
    39      private String requiredPropertyName;
    40
    41      public DynamicPropertyPointer(
    42              NodePointer parent,
    43              DynamicPropertyHandler handler)
    44      {
    45          super(parent);
    46          this.handler = handler;
    47      }
    48      /***
    49       * This type of node is auxiliary.
    50       */
    51      public boolean isContainer() {
    52          return true;
    53      }
    54
    55      /***
    56       * Number of the DP object's properties.
    57       */
    58      public int getPropertyCount() {
    59          return getPropertyNames().length;
    60      }
    61
    62      /***
    63       * Names of all properties, sorted alphabetically
    64       */
    65      public String[] getPropertyNames() {
    66          if (names == null) {
    67              String allNames[] = handler.getPropertyNames(getBean());
    68              names = new String[allNames.length];
    69              for (int i = 0; i < names.length; i++) {
    70                  names[i] = allNames[i];
    71              }
    72              Arrays.sort(names);
    73              if (requiredPropertyName != null) {
    74                  int inx = Arrays.binarySearch(names, requiredPropertyName);
    75                  if (inx < 0) {
    76                      allNames = names;
    77                      names = new String[allNames.length + 1];
    78                      names[0] = requiredPropertyName;
    79                      System.arraycopy(allNames, 0, names, 1, allNames.length);
    80                      Arrays.sort(names);
    81                  }
    82              }
    83          }
    84          return names;
    85      }
    86
    87      /***
    88       * Returns the name of the currently selected property or "*"
    89       * if none has been selected.
    90       */
    91      public String getPropertyName() {
    92          if (name == null) {
    93              String names[] = getPropertyNames();
    94              if (propertyIndex >= 0 && propertyIndex < names.length) {
    95                  name = names[propertyIndex];
    96              }
    97              else {
    98                  name = "*";
    99              }
    100         }
    101         return name;
    102     }
    103
    104     /***
    105      * Select a property by name.  If the supplied name is
    106      * not one of the object's existing properties, it implicitly
    107      * adds this name to the object's property name list. It does not
    108      * set the property value though. In order to set the property
    109      * value, call setValue().
    110      */
    111     public void setPropertyName(String propertyName) {
    112         setPropertyIndex(UNSPECIFIED_PROPERTY);
    113         this.name = propertyName;
    114         requiredPropertyName = propertyName;
    115         if (names != null && Arrays.binarySearch(names, propertyName) < 0) {
    116             names = null;
    117         }
    118     }
    119
    120     /***
    121      * Index of the currently selected property in the list of all
    122      * properties sorted alphabetically.
    123      */
    124     public int getPropertyIndex() {
    125         if (propertyIndex == UNSPECIFIED_PROPERTY) {
    126             String names[] = getPropertyNames();
    127             for (int i = 0; i < names.length; i++) {
    128                 if (names[i].equals(name)) {
    129                     setPropertyIndex(i);
    130                     break;
    131                 }
    132             }
    133         }
    134         return super.getPropertyIndex();
    135     }
    136
    137     /***
    138      * Index a property by its index in the list of all
    139      * properties sorted alphabetically.
    140      */
    141     public void setPropertyIndex(int index) {
    142         if (propertyIndex != index) {
    143             super.setPropertyIndex(index);
    144             name = null;
    145         }
    146     }
    147
    148     /***
    149      * Returns the value of the property, not an element of the collection
    150      * represented by the property, if any.
    151      */
    152     public Object getBaseValue() {
    153         return handler.getProperty(getBean(), getPropertyName());
    154     }
    155
    156     /***
    157      * If index == WHOLE_COLLECTION, the value of the property, otherwise
    158      * the value of the index'th element of the collection represented by the
    159      * property. If the property is not a collection, index should be zero
    160      * and the value will be the property itself.
    161      */
    162     public Object getImmediateNode() {
    163         Object value;
    164         if (index == WHOLE_COLLECTION) {
    165             value = ValueUtils.getValue(handler.getProperty(
    166                     getBean(),
    167                     getPropertyName()));
    168         }
    169         else {
    170             value = ValueUtils.getValue(handler.getProperty(
    171                     getBean(),
    172                     getPropertyName()), index);
    173         }
    174         return value;
    175     }
    176
    177     /***
    178      * A dynamic property is always considered actual - all keys are apparently
    179      * existing with possibly the value of null.
    180      */
    181     protected boolean isActualProperty() {
    182         return true;
    183     }
    184
    185     /***
    186      * If index == WHOLE_COLLECTION, change the value of the property, otherwise
    187      * change the value of the index'th element of the collection
    188      * represented by the property.
    189      */
    190     public void setValue(Object value) {
    191         if (index == WHOLE_COLLECTION) {
    192             handler.setProperty(getBean(), getPropertyName(), value);
    193         }
    194         else {
    195             ValueUtils.setValue(
    196                 handler.getProperty(getBean(), getPropertyName()),
    197                 index,
    198                 value);
    199         }
    200     }
    201
    202     public NodePointer createPath(JXPathContext context) {
    203         // Ignore the name passed to us, use our own data
    204         Object collection = getBaseValue();
    205         if (collection == null) {
    206             AbstractFactory factory = getAbstractFactory(context);
    207             boolean success =
    208                 factory.createObject(
    209                     context,
    210                     this,
    211                     getBean(),
    212                     getPropertyName(),
    213                     0);
    214             if (!success) {
    215                 throw new JXPathException(
    216                     "Factory could not create an object for path: " + asPath());
    217             }
    218             collection = getBaseValue();
    219         }
    220
    221         if (index != WHOLE_COLLECTION) {
    222             if (index < 0) {
    223                 throw new JXPathException("Index is less than 1: " + asPath());
    224             }
    225
    226             if (index >= getLength()) {
    227                 collection = ValueUtils.expandCollection(collection, index + 1);
    228                 handler.setProperty(getBean(), getPropertyName(), collection);
    229             }
    230         }
    231
    232         return this;
    233     }
    234
    235     public NodePointer createPath(JXPathContext context, Object value) {
    236         if (index == WHOLE_COLLECTION) {
    237             handler.setProperty(getBean(), getPropertyName(), value);
    238         }
    239         else {
    240             createPath(context);
    241             ValueUtils.setValue(getBaseValue(), index, value);
    242         }
    243         return this;
    244     }
    245
    246     public void remove() {
    247         if (index == WHOLE_COLLECTION) {
    248             removeKey();
    249         }
    250         else if (isCollection()) {
    251             Object collection = ValueUtils.remove(getBaseValue(), index);
    252             handler.setProperty(getBean(), getPropertyName(), collection);
    253         }
    254         else if (index == 0) {
    255             removeKey();
    256         }
    257     }
    258
    259     private void removeKey() {
    260         Object bean = getBean();
    261         if (bean instanceof Map) {
    262             ((Map) bean).remove(getPropertyName());
    263         }
    264         else {
    265             handler.setProperty(bean, getPropertyName(), null);
    266         }
    267     }
    268
    269     public String asPath() {
    270         StringBuffer buffer = new StringBuffer();
    271         buffer.append(getImmediateParentPointer().asPath());
    272         if (buffer.length() == 0) {
    273             buffer.append("/.");
    274         }
    275         else if (buffer.charAt(buffer.length() - 1) == '/') {
    276             buffer.append('.');
    277         }
    278         buffer.append("[@name='");
    279         buffer.append(escape(getPropertyName()));
    280         buffer.append("']");
    281         if (index != WHOLE_COLLECTION && isCollection()) {
    282             buffer.append('[').append(index + 1).append(']');
    283         }
    284         return buffer.toString();
    285     }
    286
    287     private String escape(String string) {
    288         int index = string.indexOf('\'');
    289         while (index != -1) {
    290             string =
    291                 string.substring(0, index)
    292                     + "&apos;"
    293                     + string.substring(index + 1);
    294             index = string.indexOf('\'');
    295         }
    296         index = string.indexOf('\"');
    297         while (index != -1) {
    298             string =
    299                 string.substring(0, index)
    300                     + "&quot;"
    301                     + string.substring(index + 1);
    302             index = string.indexOf('\"');
    303         }
    304         return string;
    305     }
    306
    307     private AbstractFactory getAbstractFactory(JXPathContext context) {
    308         AbstractFactory factory = context.getFactory();
    309         if (factory == null) {
    310             throw new JXPathException(
    311                 "Factory is not set on the JXPathContext - cannot create path: "
    312                     + asPath());
    313         }
    314         return factory;
    315     }
    316 }