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