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 */
017package org.apache.commons.jxpath.ri.model.dynabeans;
018
019import java.util.Arrays;
020
021import org.apache.commons.beanutils.DynaBean;
022import org.apache.commons.beanutils.DynaClass;
023import org.apache.commons.beanutils.DynaProperty;
024import org.apache.commons.jxpath.JXPathTypeConversionException;
025import org.apache.commons.jxpath.ri.model.NodePointer;
026import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
027import org.apache.commons.jxpath.util.TypeUtils;
028import 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 */
037public 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}