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    }