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