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.beans;
018    
019    import java.beans.IndexedPropertyDescriptor;
020    import java.beans.PropertyDescriptor;
021    
022    import org.apache.commons.jxpath.JXPathBeanInfo;
023    import org.apache.commons.jxpath.JXPathContext;
024    import org.apache.commons.jxpath.JXPathInvalidAccessException;
025    import org.apache.commons.jxpath.ri.model.NodePointer;
026    import org.apache.commons.jxpath.util.ValueUtils;
027    
028    /**
029     * Pointer pointing to a property of a JavaBean.
030     *
031     * @author Dmitri Plotnikov
032     * @version $Revision: 670727 $ $Date: 2008-06-23 15:10:38 -0500 (Mon, 23 Jun 2008) $
033     */
034    public class BeanPropertyPointer extends PropertyPointer {
035        private static final long serialVersionUID = -6008991447676468786L;
036    
037        private static final Object UNINITIALIZED = new Object();
038    
039        private String propertyName;
040        private JXPathBeanInfo beanInfo;
041        private Object baseValue = UNINITIALIZED;
042        private Object value = UNINITIALIZED;
043        private transient String[] names;
044        private transient PropertyDescriptor[] propertyDescriptors;
045        private transient PropertyDescriptor propertyDescriptor;
046    
047        /**
048         * Create a new BeanPropertyPointer.
049         * @param parent parent pointer
050         * @param beanInfo describes the target property/ies.
051         */
052        public BeanPropertyPointer(NodePointer parent, JXPathBeanInfo beanInfo) {
053            super(parent);
054            this.beanInfo = beanInfo;
055        }
056    
057        /**
058         * This type of node is auxiliary.
059         * @return true
060         */
061        public boolean isContainer() {
062            return true;
063        }
064    
065        public int getPropertyCount() {
066            if (beanInfo.isAtomic()) {
067                return 0;
068            }
069            return getPropertyDescriptors().length;
070        }
071    
072        /**
073         * Get the names of all properties, sorted alphabetically
074         * @return String[]
075         */
076        public String[] getPropertyNames() {
077            if (names == null) {
078                PropertyDescriptor[] pds = getPropertyDescriptors();
079                names = new String[pds.length];
080                for (int i = 0; i < names.length; i++) {
081                    names[i] = pds[i].getName();
082                }
083            }
084            return names;
085        }
086    
087        /**
088         * Select a property by name.
089         * @param propertyName String name
090         */
091        public void setPropertyName(String propertyName) {
092            setPropertyIndex(UNSPECIFIED_PROPERTY);
093            this.propertyName = propertyName;
094        }
095    
096        /**
097         * Selects a property by its offset in the alphabetically sorted list.
098         * @param index property index
099         */
100        public void setPropertyIndex(int index) {
101            if (propertyIndex != index) {
102                super.setPropertyIndex(index);
103                propertyName = null;
104                propertyDescriptor = null;
105                baseValue = UNINITIALIZED;
106                value = UNINITIALIZED;
107            }
108        }
109    
110        /**
111         * Get the value of the currently selected property.
112         * @return Object value
113         */
114        public Object getBaseValue() {
115            if (baseValue == UNINITIALIZED) {
116                PropertyDescriptor pd = getPropertyDescriptor();
117                if (pd == null) {
118                    return null;
119                }
120                baseValue = ValueUtils.getValue(getBean(), pd);
121            }
122            return baseValue;
123        }
124    
125        public void setIndex(int index) {
126            if (this.index == index) {
127                return;
128            }
129            // When dealing with a scalar, index == 0 is equivalent to
130            // WHOLE_COLLECTION, so do not change it.
131            if (this.index != WHOLE_COLLECTION
132                    || index != 0
133                    || isCollection()) {
134                super.setIndex(index);
135                value = UNINITIALIZED;
136            }
137        }
138    
139        /**
140         * If index == WHOLE_COLLECTION, the value of the property, otherwise
141         * the value of the index'th element of the collection represented by the
142         * property. If the property is not a collection, index should be zero
143         * and the value will be the property itself.
144         * @return Object
145         */
146        public Object getImmediateNode() {
147            if (value == UNINITIALIZED) {
148                if (index == WHOLE_COLLECTION) {
149                    value = ValueUtils.getValue(getBaseValue());
150                }
151                else {
152                    PropertyDescriptor pd = getPropertyDescriptor();
153                    if (pd == null) {
154                        value = null;
155                    }
156                    else {
157                        value = ValueUtils.getValue(getBean(), pd, index);
158                    }
159                }
160            }
161            return value;
162        }
163    
164        protected boolean isActualProperty() {
165            return getPropertyDescriptor() != null;
166        }
167    
168        public boolean isCollection() {
169            PropertyDescriptor pd = getPropertyDescriptor();
170            if (pd == null) {
171                return false;
172            }
173    
174            if (pd instanceof IndexedPropertyDescriptor) {
175                return true;
176            }
177    
178            int hint = ValueUtils.getCollectionHint(pd.getPropertyType());
179            if (hint == -1) {
180                return false;
181            }
182            if (hint == 1) {
183                return true;
184            }
185    
186            Object value = getBaseValue();
187            return value != null && ValueUtils.isCollection(value);
188        }
189    
190        /**
191         * If the property contains a collection, then the length of that
192         * collection, otherwise - 1.
193         * @return int length
194         */
195        public int getLength() {
196            PropertyDescriptor pd = getPropertyDescriptor();
197            if (pd == null) {
198                return 1;
199            }
200    
201            if (pd instanceof IndexedPropertyDescriptor) {
202                return ValueUtils.getIndexedPropertyLength(
203                    getBean(),
204                    (IndexedPropertyDescriptor) pd);
205            }
206    
207            int hint = ValueUtils.getCollectionHint(pd.getPropertyType());
208            if (hint == -1) {
209                return 1;
210            }
211            return ValueUtils.getLength(getBaseValue());
212        }
213    
214        /**
215         * If index == WHOLE_COLLECTION, change the value of the property, otherwise
216         * change the value of the index'th element of the collection
217         * represented by the property.
218         * @param value value to set
219         */
220        public void setValue(Object value) {
221            PropertyDescriptor pd = getPropertyDescriptor();
222            if (pd == null) {
223                throw new JXPathInvalidAccessException(
224                    "Cannot set property: " + asPath() + " - no such property");
225            }
226    
227            if (index == WHOLE_COLLECTION) {
228                ValueUtils.setValue(getBean(), pd, value);
229            }
230            else {
231                ValueUtils.setValue(getBean(), pd, index, value);
232            }
233            this.value = value;
234        }
235    
236        public NodePointer createPath(JXPathContext context) {
237            if (getImmediateNode() == null) {
238                super.createPath(context);
239                baseValue = UNINITIALIZED;
240                value = UNINITIALIZED;
241            }
242            return this;
243        }
244    
245        public void remove() {
246            if (index == WHOLE_COLLECTION) {
247                setValue(null);
248            }
249            else if (isCollection()) {
250                Object o = getBaseValue();
251                Object collection = ValueUtils.remove(getBaseValue(), index);
252                if (collection != o) {
253                    ValueUtils.setValue(getBean(), getPropertyDescriptor(), collection);
254                }
255            }
256            else if (index == 0) {
257                index = WHOLE_COLLECTION;
258                setValue(null);
259            }
260        }
261    
262        /**
263         * Get the name of the currently selected property.
264         * @return String property name
265         */
266        public String getPropertyName() {
267            if (propertyName == null) {
268                PropertyDescriptor pd = getPropertyDescriptor();
269                if (pd != null) {
270                    propertyName = pd.getName();
271                }
272            }
273            return propertyName != null ? propertyName : "*";
274        }
275    
276        /**
277         * Finds the property descriptor corresponding to the current property
278         * index.
279         * @return PropertyDescriptor
280         */
281        private PropertyDescriptor getPropertyDescriptor() {
282            if (propertyDescriptor == null) {
283                int inx = getPropertyIndex();
284                if (inx == UNSPECIFIED_PROPERTY) {
285                    propertyDescriptor =
286                        beanInfo.getPropertyDescriptor(propertyName);
287                }
288                else {
289                    PropertyDescriptor[] propertyDescriptors =
290                        getPropertyDescriptors();
291                    if (inx >= 0 && inx < propertyDescriptors.length) {
292                        propertyDescriptor = propertyDescriptors[inx];
293                    }
294                    else {
295                        propertyDescriptor = null;
296                    }
297                }
298            }
299            return propertyDescriptor;
300        }
301    
302        /**
303         * Get all PropertyDescriptors.
304         * @return PropertyDescriptor[]
305         */
306        protected synchronized PropertyDescriptor[] getPropertyDescriptors() {
307            if (propertyDescriptors == null) {
308                propertyDescriptors = beanInfo.getPropertyDescriptors();
309            }
310            return propertyDescriptors;
311        }
312    }