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  
18  package org.apache.commons.jxpath.ri.model.beans;
19  
20  import java.beans.IndexedPropertyDescriptor;
21  import java.beans.PropertyDescriptor;
22  
23  import org.apache.commons.jxpath.JXPathBeanInfo;
24  import org.apache.commons.jxpath.JXPathContext;
25  import org.apache.commons.jxpath.JXPathInvalidAccessException;
26  import org.apache.commons.jxpath.ri.model.NodePointer;
27  import org.apache.commons.jxpath.util.ValueUtils;
28  
29  /**
30   * Pointer to a property of a JavaBean.
31   */
32  public class BeanPropertyPointer extends PropertyPointer {
33  
34      private static final long serialVersionUID = -6008991447676468786L;
35  
36      /**
37       * Uninitialized object marker.
38       */
39      private static final Object UNINITIALIZED = new Object();
40  
41      /**
42       * The name of the currently selected property.
43       */
44      private String propertyName;
45  
46      /**
47       * JavaBean info.
48       */
49      private final JXPathBeanInfo beanInfo;
50  
51      /**
52       * The value of the currently selected property..
53       */
54      private Object baseValue = UNINITIALIZED;
55  
56      /**
57       * If index == WHOLE_COLLECTION, the value of the property, otherwise the value of the index'th element of the collection represented by the property. If
58       * the property is not a collection, index should be zero and the value will be the property itself.
59       */
60      private Object value = UNINITIALIZED;
61  
62      /**
63       * The names of all properties, sorted alphabetically.
64       */
65      private transient String[] names;
66  
67      /**
68       * All PropertyDescriptors.
69       */
70      private transient PropertyDescriptor[] propertyDescriptors;
71  
72      /**
73       * The property descriptor corresponding to the current property index.
74       */
75      private transient PropertyDescriptor propertyDescriptor;
76  
77      /**
78       * Constructs a new BeanPropertyPointer.
79       *
80       * @param parent   parent pointer
81       * @param beanInfo describes the target property/ies.
82       */
83      public BeanPropertyPointer(final NodePointer parent, final JXPathBeanInfo beanInfo) {
84          super(parent);
85          this.beanInfo = beanInfo;
86      }
87  
88      @Override
89      public NodePointer createPath(final JXPathContext context) {
90          if (getImmediateNode() == null) {
91              super.createPath(context);
92              baseValue = UNINITIALIZED;
93              value = UNINITIALIZED;
94          }
95          return this;
96      }
97  
98      /**
99       * Gets the value of the currently selected property.
100      *
101      * @return Object value
102      */
103     @Override
104     public Object getBaseValue() {
105         if (baseValue == UNINITIALIZED) {
106             final PropertyDescriptor pd = getPropertyDescriptor();
107             if (pd == null) {
108                 return null;
109             }
110             baseValue = ValueUtils.getValue(getBean(), pd);
111         }
112         return baseValue;
113     }
114 
115     /**
116      * If index == WHOLE_COLLECTION, the value of the property, otherwise the value of the index'th element of the collection represented by the property. If
117      * the property is not a collection, index should be zero and the value will be the property itself.
118      *
119      * @return Object
120      */
121     @Override
122     public Object getImmediateNode() {
123         if (value == UNINITIALIZED) {
124             if (index == WHOLE_COLLECTION) {
125                 value = ValueUtils.getValue(getBaseValue());
126             } else {
127                 final PropertyDescriptor pd = getPropertyDescriptor();
128                 if (pd == null) {
129                     value = null;
130                 } else {
131                     value = ValueUtils.getValue(getBean(), pd, index);
132                 }
133             }
134         }
135         return value;
136     }
137 
138     /**
139      * If the property contains a collection, then the length of that collection, otherwise - 1.
140      *
141      * @return int length
142      */
143     @Override
144     public int getLength() {
145         final PropertyDescriptor pd = getPropertyDescriptor();
146         if (pd == null) {
147             return 1;
148         }
149         if (pd instanceof IndexedPropertyDescriptor) {
150             return ValueUtils.getIndexedPropertyLength(getBean(), (IndexedPropertyDescriptor) pd);
151         }
152         final int hint = ValueUtils.getCollectionHint(pd.getPropertyType());
153         if (hint == -1) {
154             return 1;
155         }
156         return super.getLength();
157     }
158 
159     @Override
160     public int getPropertyCount() {
161         if (beanInfo.isAtomic()) {
162             return 0;
163         }
164         return getPropertyDescriptors().length;
165     }
166 
167     /**
168      * Gets the property descriptor corresponding to the current property index.
169      *
170      * @return PropertyDescriptor
171      */
172     private PropertyDescriptor getPropertyDescriptor() {
173         if (propertyDescriptor == null) {
174             final int inx = getPropertyIndex();
175             if (inx == UNSPECIFIED_PROPERTY) {
176                 propertyDescriptor = beanInfo.getPropertyDescriptor(propertyName);
177             } else {
178                 final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors();
179                 if (inx >= 0 && inx < propertyDescriptors.length) {
180                     propertyDescriptor = propertyDescriptors[inx];
181                 } else {
182                     propertyDescriptor = null;
183                 }
184             }
185         }
186         return propertyDescriptor;
187     }
188 
189     /**
190      * Gets all PropertyDescriptors.
191      *
192      * @return PropertyDescriptor[]
193      */
194     protected synchronized PropertyDescriptor[] getPropertyDescriptors() {
195         if (propertyDescriptors == null) {
196             propertyDescriptors = beanInfo.getPropertyDescriptors();
197         }
198         return propertyDescriptors;
199     }
200 
201     /**
202      * Gets the name of the currently selected property.
203      *
204      * @return String property name
205      */
206     @Override
207     public String getPropertyName() {
208         if (propertyName == null) {
209             final PropertyDescriptor pd = getPropertyDescriptor();
210             if (pd != null) {
211                 propertyName = pd.getName();
212             }
213         }
214         return propertyName != null ? propertyName : "*";
215     }
216 
217     /**
218      * Gets the names of all properties, sorted alphabetically.
219      *
220      * @return String[]
221      */
222     @Override
223     public String[] getPropertyNames() {
224         if (names == null) {
225             final PropertyDescriptor[] pds = getPropertyDescriptors();
226             names = new String[pds.length];
227             for (int i = 0; i < names.length; i++) {
228                 names[i] = pds[i].getName();
229             }
230         }
231         return names;
232     }
233 
234     @Override
235     protected boolean isActualProperty() {
236         return getPropertyDescriptor() != null;
237     }
238 
239     @Override
240     public boolean isCollection() {
241         final PropertyDescriptor pd = getPropertyDescriptor();
242         if (pd == null) {
243             return false;
244         }
245         if (pd instanceof IndexedPropertyDescriptor) {
246             return true;
247         }
248         final int hint = ValueUtils.getCollectionHint(pd.getPropertyType());
249         if (hint == -1) {
250             return false;
251         }
252         if (hint == 1) {
253             return true;
254         }
255         final Object value = getBaseValue();
256         return value != null && ValueUtils.isCollection(value);
257     }
258 
259     /**
260      * This type of node is auxiliary.
261      *
262      * @return true
263      */
264     @Override
265     public boolean isContainer() {
266         return true;
267     }
268 
269     @Override
270     public void remove() {
271         if (index == WHOLE_COLLECTION) {
272             setValue(null);
273         } else if (isCollection()) {
274             final Object o = getBaseValue();
275             final Object collection = ValueUtils.remove(getBaseValue(), index);
276             if (collection != o) {
277                 ValueUtils.setValue(getBean(), getPropertyDescriptor(), collection);
278             }
279         } else if (index == 0) {
280             index = WHOLE_COLLECTION;
281             setValue(null);
282         }
283     }
284 
285     @Override
286     public void setIndex(final int index) {
287         if (this.index == index) {
288             return;
289         }
290         // When dealing with a scalar, index == 0 is equivalent to
291         // WHOLE_COLLECTION, so do not change it.
292         if (this.index != WHOLE_COLLECTION || index != 0 || isCollection()) {
293             super.setIndex(index);
294             value = UNINITIALIZED;
295         }
296     }
297 
298     /**
299      * Selects a property by its offset in the alphabetically sorted list.
300      *
301      * @param index property index
302      */
303     @Override
304     public void setPropertyIndex(final int index) {
305         if (propertyIndex != index) {
306             super.setPropertyIndex(index);
307             propertyName = null;
308             propertyDescriptor = null;
309             baseValue = UNINITIALIZED;
310             value = UNINITIALIZED;
311         }
312     }
313 
314     /**
315      * Select a property by name.
316      *
317      * @param propertyName String name
318      */
319     @Override
320     public void setPropertyName(final String propertyName) {
321         setPropertyIndex(UNSPECIFIED_PROPERTY);
322         this.propertyName = propertyName;
323     }
324 
325     /**
326      * If index == WHOLE_COLLECTION, change the value of the property, otherwise change the value of the index'th element of the collection represented by the
327      * property.
328      *
329      * @param value value to set
330      */
331     @Override
332     public void setValue(final Object value) {
333         final PropertyDescriptor pd = getPropertyDescriptor();
334         if (pd == null) {
335             throw new JXPathInvalidAccessException("Cannot set property: " + asPath() + " - no such property");
336         }
337         if (index == WHOLE_COLLECTION) {
338             ValueUtils.setValue(getBean(), pd, value);
339         } else {
340             ValueUtils.setValue(getBean(), pd, index, value);
341         }
342         this.value = value;
343     }
344 }