    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.dynabeans;
    018
    019    import java.util.Arrays;
    020
    021    import org.apache.commons.beanutils.DynaBean;
    022    import org.apache.commons.beanutils.DynaClass;
    023    import org.apache.commons.beanutils.DynaProperty;
    024    import org.apache.commons.jxpath.JXPathTypeConversionException;
    025    import org.apache.commons.jxpath.ri.model.NodePointer;
    026    import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
    027    import org.apache.commons.jxpath.util.TypeUtils;
    028    import org.apache.commons.jxpath.util.ValueUtils;
    029
    030    /**
    031     * Pointer pointing to a property of a {@link DynaBean}. If the target DynaBean is
    032     * Serializable, so should this instance be.
    033     *
    034     * @author Dmitri Plotnikov
    035     * @version $Revision: 668329 $ $Date: 2008-06-16 16:59:48 -0500 (Mon, 16 Jun 2008) $
    036     */
    037    public class DynaBeanPropertyPointer extends PropertyPointer {
    038        private DynaBean dynaBean;
    039        private String name;
    040        private String[] names;
    041
    042        private static final long serialVersionUID = 2094421509141267239L;
    043
    044        /**
    045         * Create a new DynaBeanPropertyPointer.
    046         * @param parent pointer
    047         * @param dynaBean pointed
    048         */
    049        public DynaBeanPropertyPointer(NodePointer parent, DynaBean dynaBean) {
    050            super(parent);
    051            this.dynaBean = dynaBean;
    052        }
    053
    054        public Object getBaseValue() {
    055            return dynaBean.get(getPropertyName());
    056        }
    057
    058        /**
    059         * This type of node is auxiliary.
    060         * @return true
    061         */
    062        public boolean isContainer() {
    063            return true;
    064        }
    065
    066        public int getPropertyCount() {
    067            return getPropertyNames().length;
    068        }
    069
    070        public String[] getPropertyNames() {
    071            /* @todo do something about the sorting - LIKE WHAT? - MJB */
    072            if (names == null) {
    073                DynaClass dynaClass = dynaBean.getDynaClass();
    074                DynaProperty[] properties = dynaClass.getDynaProperties();
    075                int count = properties.length;
    076                boolean hasClass = dynaClass.getDynaProperty("class") != null;
    077                if (hasClass) {
    078                    count--;       // Exclude "class" from properties
    079                }
    080                names = new String[count];
    081                for (int i = 0, j = 0; i < properties.length; i++) {
    082                    String name = properties[i].getName();
    083                    if (!hasClass || !name.equals("class")) {
    084                        names[j++] = name;
    085                    }
    086                }
    087                Arrays.sort(names);
    088            }
    089            return names;
    090        }
    091
    092        /**
    093         * Returns the name of the currently selected property or "*"
    094         * if none has been selected.
    095         * @return String
    096         */
    097        public String getPropertyName() {
    098            if (name == null) {
    099                String[] names = getPropertyNames();
    100                name = propertyIndex >= 0 && propertyIndex < names.length ? names[propertyIndex] : "*";
    101            }
    102            return name;
    103        }
    104
    105        /**
    106         * Select a property by name.
    107         * @param propertyName to select
    108         */
    109        public void setPropertyName(String propertyName) {
    110            setPropertyIndex(UNSPECIFIED_PROPERTY);
    111            this.name = propertyName;
    112        }
    113
    114        /**
    115         * Index of the currently selected property in the list of all
    116         * properties sorted alphabetically.
    117         * @return int
    118         */
    119        public int getPropertyIndex() {
    120            if (propertyIndex == UNSPECIFIED_PROPERTY) {
    121                String[] names = getPropertyNames();
    122                for (int i = 0; i < names.length; i++) {
    123                    if (names[i].equals(name)) {
    124                        propertyIndex = i;
    125                        name = null;
    126                        break;
    127                    }
    128                }
    129            }
    130            return super.getPropertyIndex();
    131        }
    132
    133        /**
    134         * Index a property by its index in the list of all
    135         * properties sorted alphabetically.
    136         * @param index to set
    137         */
    138        public void setPropertyIndex(int index) {
    139            if (propertyIndex != index) {
    140                super.setPropertyIndex(index);
    141                name = null;
    142            }
    143        }
    144
    145        /**
    146         * If index == WHOLE_COLLECTION, the value of the property, otherwise
    147         * the value of the index'th element of the collection represented by the
    148         * property. If the property is not a collection, index should be zero
    149         * and the value will be the property itself.
    150         * @return Object
    151         */
    152        public Object getImmediateNode() {
    153            String name = getPropertyName();
    154            if (name.equals("*")) {
    155                return null;
    156            }
    157
    158            Object value;
    159            if (index == WHOLE_COLLECTION) {
    160                value = ValueUtils.getValue(dynaBean.get(name));
    161            }
    162            else if (isIndexedProperty()) {
    163                // DynaClass at this point is not based on whether
    164                // the property is indeed indexed, but rather on
    165                // whether it is an array or List. Therefore
    166                // the indexed set may fail.
    167                try {
    168                    value = ValueUtils.getValue(dynaBean.get(name, index));
    169                }
    170                catch (ArrayIndexOutOfBoundsException ex) {
    171                    value = null;
    172                }
    173                catch (IllegalArgumentException ex) {
    174                    value = dynaBean.get(name);
    175                    value = ValueUtils.getValue(value, index);
    176                }
    177            }
    178            else {
    179                value = dynaBean.get(name);
    180                if (ValueUtils.isCollection(value)) {
    181                    value = ValueUtils.getValue(value, index);
    182                }
    183                else if (index != 0) {
    184                    value = null;
    185                }
    186            }
    187            return value;
    188        }
    189
    190        /**
    191         * Returns true if the bean has the currently selected property.
    192         * @return boolean
    193         */
    194        protected boolean isActualProperty() {
    195            DynaClass dynaClass = dynaBean.getDynaClass();
    196            return dynaClass.getDynaProperty(getPropertyName()) != null;
    197        }
    198
    199        /**
    200         * Learn whether the property referenced is an indexed property.
    201         * @return boolean
    202         */
    203        protected boolean isIndexedProperty() {
    204            DynaClass dynaClass = dynaBean.getDynaClass();
    205            DynaProperty property = dynaClass.getDynaProperty(name);
    206            return property.isIndexed();
    207        }
    208
    209        /**
    210         * If index == WHOLE_COLLECTION, change the value of the property, otherwise
    211         * change the value of the index'th element of the collection
    212         * represented by the property.
    213         * @param value to set
    214         */
    215        public void setValue(Object value) {
    216            setValue(index, value);
    217        }
    218
    219        public void remove() {
    220            if (index == WHOLE_COLLECTION) {
    221                dynaBean.set(getPropertyName(), null);
    222            }
    223            else if (isIndexedProperty()) {
    224                dynaBean.set(getPropertyName(), index, null);
    225            }
    226            else if (isCollection()) {
    227                Object collection = ValueUtils.remove(getBaseValue(), index);
    228                dynaBean.set(getPropertyName(), collection);
    229            }
    230            else if (index == 0) {
    231                dynaBean.set(getPropertyName(), null);
    232            }
    233        }
    234
    235        /**
    236         * Set an indexed value.
    237         * @param index to change
    238         * @param value to set
    239         */
    240        private void setValue(int index, Object value) {
    241            if (index == WHOLE_COLLECTION) {
    242                dynaBean.set(getPropertyName(), convert(value, false));
    243            }
    244            else if (isIndexedProperty()) {
    245                dynaBean.set(getPropertyName(), index, convert(value, true));
    246            }
    247            else {
    248                Object baseValue = dynaBean.get(getPropertyName());
    249                ValueUtils.setValue(baseValue, index, value);
    250            }
    251        }
    252
    253
    254        /**
    255         * Convert a value to the appropriate property type.
    256         * @param value to convert
    257         * @param element whether this should be a collection element.
    258         * @return conversion result
    259         */
    260        private Object convert(Object value, boolean element) {
    261            DynaClass dynaClass = (DynaClass) dynaBean.getDynaClass();
    262            DynaProperty property = dynaClass.getDynaProperty(getPropertyName());
    263            Class type = property.getType();
    264            if (element) {
    265                if (type.isArray()) {
    266                    type = type.getComponentType();
    267                }
    268                else {
    269                    return value; // No need to convert
    270                }
    271            }
    272
    273            try {
    274                return TypeUtils.convert(value, type);
    275            }
    276            catch (Exception ex) {
    277                String string = value == null ? "null" : value.getClass().getName();
    278                throw new JXPathTypeConversionException(
    279                        "Cannot convert value of class " + string + " to type "
    280                                + type, ex);
    281            }
    282        }
    283    }