View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jxpath.ri.model.beans;
18  
19  import java.beans.IndexedPropertyDescriptor;
20  import java.beans.PropertyDescriptor;
21  
22  import org.apache.commons.jxpath.JXPathBeanInfo;
23  import org.apache.commons.jxpath.JXPathContext;
24  import org.apache.commons.jxpath.JXPathInvalidAccessException;
25  import org.apache.commons.jxpath.ri.model.NodePointer;
26  import org.apache.commons.jxpath.util.ValueUtils;
27  
28  /**
29   * Pointer pointing to a property of a JavaBean.
30   *
31   * @author Dmitri Plotnikov
32   * @version $Revision: 1133499 $ $Date: 2011-06-08 20:19:50 +0200 (Mi, 08 Jun 2011) $
33   */
34  public class BeanPropertyPointer extends PropertyPointer {
35      private static final long serialVersionUID = -6008991447676468786L;
36  
37      private static final Object UNINITIALIZED = new Object();
38  
39      private String propertyName;
40      private JXPathBeanInfo beanInfo;
41      private Object baseValue = UNINITIALIZED;
42      private Object value = UNINITIALIZED;
43      private transient String[] names;
44      private transient PropertyDescriptor[] propertyDescriptors;
45      private transient PropertyDescriptor propertyDescriptor;
46  
47      /**
48       * Create a new BeanPropertyPointer.
49       * @param parent parent pointer
50       * @param beanInfo describes the target property/ies.
51       */
52      public BeanPropertyPointer(NodePointer parent, JXPathBeanInfo beanInfo) {
53          super(parent);
54          this.beanInfo = beanInfo;
55      }
56  
57      /**
58       * This type of node is auxiliary.
59       * @return true
60       */
61      public boolean isContainer() {
62          return true;
63      }
64  
65      public int getPropertyCount() {
66          if (beanInfo.isAtomic()) {
67              return 0;
68          }
69          return getPropertyDescriptors().length;
70      }
71  
72      /**
73       * Get the names of all properties, sorted alphabetically
74       * @return String[]
75       */
76      public String[] getPropertyNames() {
77          if (names == null) {
78              PropertyDescriptor[] pds = getPropertyDescriptors();
79              names = new String[pds.length];
80              for (int i = 0; i < names.length; i++) {
81                  names[i] = pds[i].getName();
82              }
83          }
84          return names;
85      }
86  
87      /**
88       * Select a property by name.
89       * @param propertyName String name
90       */
91      public void setPropertyName(String propertyName) {
92          setPropertyIndex(UNSPECIFIED_PROPERTY);
93          this.propertyName = propertyName;
94      }
95  
96      /**
97       * Selects a property by its offset in the alphabetically sorted list.
98       * @param index property index
99       */
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 super.getLength();
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 }