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.dynabeans;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  
22  import org.apache.commons.beanutils.DynaBean;
23  import org.apache.commons.beanutils.DynaClass;
24  import org.apache.commons.beanutils.DynaProperty;
25  import org.apache.commons.jxpath.JXPathTypeConversionException;
26  import org.apache.commons.jxpath.ri.model.NodePointer;
27  import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
28  import org.apache.commons.jxpath.util.TypeUtils;
29  import org.apache.commons.jxpath.util.ValueUtils;
30  
31  /**
32   * Pointer pointing to a property of a {@link DynaBean}. If the target DynaBean is
33   * Serializable, so should this instance be.
34   *
35   * @author Dmitri Plotnikov
36   * @version $Revision: 1523199 $ $Date: 2013-09-14 11:45:47 +0200 (Sa, 14 Sep 2013) $
37   */
38  public class DynaBeanPropertyPointer extends PropertyPointer {
39      private static final String CLASS = "class";
40  
41      private DynaBean dynaBean;
42      private String name;
43      private String[] names;
44  
45      private static final long serialVersionUID = 2094421509141267239L;
46  
47      /**
48       * Create a new DynaBeanPropertyPointer.
49       * @param parent pointer
50       * @param dynaBean pointed
51       */
52      public DynaBeanPropertyPointer(NodePointer parent, DynaBean dynaBean) {
53          super(parent);
54          this.dynaBean = dynaBean;
55      }
56  
57      public Object getBaseValue() {
58          return dynaBean.get(getPropertyName());
59      }
60  
61      /**
62       * This type of node is auxiliary.
63       * @return true
64       */
65      public boolean isContainer() {
66          return true;
67      }
68  
69      public int getPropertyCount() {
70          return getPropertyNames().length;
71      }
72  
73      public String[] getPropertyNames() {
74          /* @todo do something about the sorting - LIKE WHAT? - MJB */
75          if (names == null) {
76              DynaClass dynaClass = dynaBean.getDynaClass();
77              DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
78              ArrayList properties = new ArrayList(dynaProperties.length);
79              for (int i = 0; i < dynaProperties.length; i++) {
80                  String name = dynaProperties[i].getName();
81                  if (!CLASS.equals(name)) {
82                      properties.add(name);
83                  }
84              }
85              names = (String[]) properties.toArray(new String[properties.size()]);
86              Arrays.sort(names);
87          }
88          return names;
89      }
90  
91      /**
92       * Returns the name of the currently selected property or "*"
93       * if none has been selected.
94       * @return String
95       */
96      public String getPropertyName() {
97          if (name == null) {
98              String[] names = getPropertyNames();
99              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 }