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