    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.beans;
    018
    019    import java.beans.IndexedPropertyDescriptor;
    020    import java.beans.PropertyDescriptor;
    021
    022    import org.apache.commons.jxpath.JXPathBeanInfo;
    023    import org.apache.commons.jxpath.JXPathContext;
    024    import org.apache.commons.jxpath.JXPathInvalidAccessException;
    025    import org.apache.commons.jxpath.ri.model.NodePointer;
    026    import org.apache.commons.jxpath.util.ValueUtils;
    027
    028    /**
    029     * Pointer pointing to a property of a JavaBean.
    030     *
    031     * @author Dmitri Plotnikov
    032     * @version $Revision: 670727 $ $Date: 2008-06-23 15:10:38 -0500 (Mon, 23 Jun 2008) $
    033     */
    034    public class BeanPropertyPointer extends PropertyPointer {
    035        private static final long serialVersionUID = -6008991447676468786L;
    036
    037        private static final Object UNINITIALIZED = new Object();
    038
    039        private String propertyName;
    040        private JXPathBeanInfo beanInfo;
    041        private Object baseValue = UNINITIALIZED;
    042        private Object value = UNINITIALIZED;
    043        private transient String[] names;
    044        private transient PropertyDescriptor[] propertyDescriptors;
    045        private transient PropertyDescriptor propertyDescriptor;
    046
    047        /**
    048         * Create a new BeanPropertyPointer.
    049         * @param parent parent pointer
    050         * @param beanInfo describes the target property/ies.
    051         */
    052        public BeanPropertyPointer(NodePointer parent, JXPathBeanInfo beanInfo) {
    053            super(parent);
    054            this.beanInfo = beanInfo;
    055        }
    056
    057        /**
    058         * This type of node is auxiliary.
    059         * @return true
    060         */
    061        public boolean isContainer() {
    062            return true;
    063        }
    064
    065        public int getPropertyCount() {
    066            if (beanInfo.isAtomic()) {
    067                return 0;
    068            }
    069            return getPropertyDescriptors().length;
    070        }
    071
    072        /**
    073         * Get the names of all properties, sorted alphabetically
    074         * @return String[]
    075         */
    076        public String[] getPropertyNames() {
    077            if (names == null) {
    078                PropertyDescriptor[] pds = getPropertyDescriptors();
    079                names = new String[pds.length];
    080                for (int i = 0; i < names.length; i++) {
    081                    names[i] = pds[i].getName();
    082                }
    083            }
    084            return names;
    085        }
    086
    087        /**
    088         * Select a property by name.
    089         * @param propertyName String name
    090         */
    091        public void setPropertyName(String propertyName) {
    092            setPropertyIndex(UNSPECIFIED_PROPERTY);
    093            this.propertyName = propertyName;
    094        }
    095
    096        /**
    097         * Selects a property by its offset in the alphabetically sorted list.
    098         * @param index property index
    099         */
    100        public void setPropertyIndex(int index) {
    101            if (propertyIndex != index) {
    102                super.setPropertyIndex(index);
    103                propertyName = null;
    104                propertyDescriptor = null;
    105                baseValue = UNINITIALIZED;
    106                value = UNINITIALIZED;
    107            }
    108        }
    109
    110        /**
    111         * Get the value of the currently selected property.
    112         * @return Object value
    113         */
    114        public Object getBaseValue() {
    115            if (baseValue == UNINITIALIZED) {
    116                PropertyDescriptor pd = getPropertyDescriptor();
    117                if (pd == null) {
    118                    return null;
    119                }
    120                baseValue = ValueUtils.getValue(getBean(), pd);
    121            }
    122            return baseValue;
    123        }
    124
    125        public void setIndex(int index) {
    126            if (this.index == index) {
    127                return;
    128            }
    129            // When dealing with a scalar, index == 0 is equivalent to
    130            // WHOLE_COLLECTION, so do not change it.
    131            if (this.index != WHOLE_COLLECTION
    132                    || index != 0
    133                    || isCollection()) {
    134                super.setIndex(index);
    135                value = UNINITIALIZED;
    136            }
    137        }
    138
    139        /**
    140         * If index == WHOLE_COLLECTION, the value of the property, otherwise
    141         * the value of the index'th element of the collection represented by the
    142         * property. If the property is not a collection, index should be zero
    143         * and the value will be the property itself.
    144         * @return Object
    145         */
    146        public Object getImmediateNode() {
    147            if (value == UNINITIALIZED) {
    148                if (index == WHOLE_COLLECTION) {
    149                    value = ValueUtils.getValue(getBaseValue());
    150                }
    151                else {
    152                    PropertyDescriptor pd = getPropertyDescriptor();
    153                    if (pd == null) {
    154                        value = null;
    155                    }
    156                    else {
    157                        value = ValueUtils.getValue(getBean(), pd, index);
    158                    }
    159                }
    160            }
    161            return value;
    162        }
    163
    164        protected boolean isActualProperty() {
    165            return getPropertyDescriptor() != null;
    166        }
    167
    168        public boolean isCollection() {
    169            PropertyDescriptor pd = getPropertyDescriptor();
    170            if (pd == null) {
    171                return false;
    172            }
    173
    174            if (pd instanceof IndexedPropertyDescriptor) {
    175                return true;
    176            }
    177
    178            int hint = ValueUtils.getCollectionHint(pd.getPropertyType());
    179            if (hint == -1) {
    180                return false;
    181            }
    182            if (hint == 1) {
    183                return true;
    184            }
    185
    186            Object value = getBaseValue();
    187            return value != null && ValueUtils.isCollection(value);
    188        }
    189
    190        /**
    191         * If the property contains a collection, then the length of that
    192         * collection, otherwise - 1.
    193         * @return int length
    194         */
    195        public int getLength() {
    196            PropertyDescriptor pd = getPropertyDescriptor();
    197            if (pd == null) {
    198                return 1;
    199            }
    200
    201            if (pd instanceof IndexedPropertyDescriptor) {
    202                return ValueUtils.getIndexedPropertyLength(
    203                    getBean(),
    204                    (IndexedPropertyDescriptor) pd);
    205            }
    206
    207            int hint = ValueUtils.getCollectionHint(pd.getPropertyType());
    208            if (hint == -1) {
    209                return 1;
    210            }
    211            return ValueUtils.getLength(getBaseValue());
    212        }
    213
    214        /**
    215         * If index == WHOLE_COLLECTION, change the value of the property, otherwise
    216         * change the value of the index'th element of the collection
    217         * represented by the property.
    218         * @param value value to set
    219         */
    220        public void setValue(Object value) {
    221            PropertyDescriptor pd = getPropertyDescriptor();
    222            if (pd == null) {
    223                throw new JXPathInvalidAccessException(
    224                    "Cannot set property: " + asPath() + " - no such property");
    225            }
    226
    227            if (index == WHOLE_COLLECTION) {
    228                ValueUtils.setValue(getBean(), pd, value);
    229            }
    230            else {
    231                ValueUtils.setValue(getBean(), pd, index, value);
    232            }
    233            this.value = value;
    234        }
    235
    236        public NodePointer createPath(JXPathContext context) {
    237            if (getImmediateNode() == null) {
    238                super.createPath(context);
    239                baseValue = UNINITIALIZED;
    240                value = UNINITIALIZED;
    241            }
    242            return this;
    243        }
    244
    245        public void remove() {
    246            if (index == WHOLE_COLLECTION) {
    247                setValue(null);
    248            }
    249            else if (isCollection()) {
    250                Object o = getBaseValue();
    251                Object collection = ValueUtils.remove(getBaseValue(), index);
    252                if (collection != o) {
    253                    ValueUtils.setValue(getBean(), getPropertyDescriptor(), collection);
    254                }
    255            }
    256            else if (index == 0) {
    257                index = WHOLE_COLLECTION;
    258                setValue(null);
    259            }
    260        }
    261
    262        /**
    263         * Get the name of the currently selected property.
    264         * @return String property name
    265         */
    266        public String getPropertyName() {
    267            if (propertyName == null) {
    268                PropertyDescriptor pd = getPropertyDescriptor();
    269                if (pd != null) {
    270                    propertyName = pd.getName();
    271                }
    272            }
    273            return propertyName != null ? propertyName : "*";
    274        }
    275
    276        /**
    277         * Finds the property descriptor corresponding to the current property
    278         * index.
    279         * @return PropertyDescriptor
    280         */
    281        private PropertyDescriptor getPropertyDescriptor() {
    282            if (propertyDescriptor == null) {
    283                int inx = getPropertyIndex();
    284                if (inx == UNSPECIFIED_PROPERTY) {
    285                    propertyDescriptor =
    286                        beanInfo.getPropertyDescriptor(propertyName);
    287                }
    288                else {
    289                    PropertyDescriptor[] propertyDescriptors =
    290                        getPropertyDescriptors();
    291                    if (inx >= 0 && inx < propertyDescriptors.length) {
    292                        propertyDescriptor = propertyDescriptors[inx];
    293                    }
    294                    else {
    295                        propertyDescriptor = null;
    296                    }
    297                }
    298            }
    299            return propertyDescriptor;
    300        }
    301
    302        /**
    303         * Get all PropertyDescriptors.
    304         * @return PropertyDescriptor[]
    305         */
    306        protected synchronized PropertyDescriptor[] getPropertyDescriptors() {
    307            if (propertyDescriptors == null) {
    308                propertyDescriptors = beanInfo.getPropertyDescriptors();
    309            }
    310            return propertyDescriptors;
    311        }
    312    }