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.dynabeans;
19  
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  
23  import org.apache.commons.beanutils.DynaBean;
24  import org.apache.commons.beanutils.DynaClass;
25  import org.apache.commons.beanutils.DynaProperty;
26  import org.apache.commons.jxpath.JXPathTypeConversionException;
27  import org.apache.commons.jxpath.ri.model.NodePointer;
28  import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
29  import org.apache.commons.jxpath.util.TypeUtils;
30  import org.apache.commons.jxpath.util.ValueUtils;
31  
32  /**
33   * Pointer to a property of a {@link DynaBean}. If the target DynaBean is Serializable, so should this instance be.
34   */
35  public class DynaBeanPropertyPointer extends PropertyPointer {
36  
37      private static final String CLASS = "class";
38      private static final long serialVersionUID = 2094421509141267239L;
39  
40      /**
41       * DynaBean.
42       */
43      private final DynaBean dynaBean;
44  
45      /**
46       * The name of the currently selected property or "*" if none has been selected.
47       */
48      private String name;
49  
50      /**
51       * The names of the included properties.
52       */
53      private String[] names;
54  
55      /**
56       * Constructs a new DynaBeanPropertyPointer.
57       *
58       * @param parent   pointer
59       * @param dynaBean pointed
60       */
61      public DynaBeanPropertyPointer(final NodePointer parent, final DynaBean dynaBean) {
62          super(parent);
63          this.dynaBean = dynaBean;
64      }
65  
66      /**
67       * Convert a value to the appropriate property type.
68       *
69       * @param value   to convert
70       * @param element whether this should be a collection element.
71       * @return conversion result
72       */
73      private Object convert(final Object value, final boolean element) {
74          final DynaClass dynaClass = dynaBean.getDynaClass();
75          final DynaProperty property = dynaClass.getDynaProperty(getPropertyName());
76          Class type = property.getType();
77          if (element) {
78              if (!type.isArray()) {
79                  return value; // No need to convert
80              }
81              type = type.getComponentType();
82          }
83          try {
84              return TypeUtils.convert(value, type);
85          } catch (final Exception ex) {
86              final String string = value == null ? "null" : value.getClass().getName();
87              throw new JXPathTypeConversionException("Cannot convert value of class " + string + " to type " + type, ex);
88          }
89      }
90  
91      @Override
92      public Object getBaseValue() {
93          return dynaBean.get(getPropertyName());
94      }
95  
96      /**
97       * 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
98       * the property is not a collection, index should be zero and the value will be the property itself.
99       *
100      * @return Object
101      */
102     @Override
103     public Object getImmediateNode() {
104         final String name = getPropertyName();
105         if (name.equals("*")) {
106             return null;
107         }
108         Object value;
109         if (index == WHOLE_COLLECTION) {
110             value = ValueUtils.getValue(dynaBean.get(name));
111         } else if (isIndexedProperty()) {
112             // DynaClass at this point is not based on whether
113             // the property is indeed indexed, but rather on
114             // whether it is an array or List. Therefore
115             // the indexed set may fail.
116             try {
117                 value = ValueUtils.getValue(dynaBean.get(name, index));
118             } catch (final ArrayIndexOutOfBoundsException ex) {
119                 value = null;
120             } catch (final IllegalArgumentException ex) {
121                 value = dynaBean.get(name);
122                 value = ValueUtils.getValue(value, index);
123             }
124         } else {
125             value = dynaBean.get(name);
126             if (ValueUtils.isCollection(value)) {
127                 value = ValueUtils.getValue(value, index);
128             } else if (index != 0) {
129                 value = null;
130             }
131         }
132         return value;
133     }
134 
135     @Override
136     public int getPropertyCount() {
137         return getPropertyNames().length;
138     }
139 
140     /**
141      * Index of the currently selected property in the list of all properties sorted alphabetically.
142      *
143      * @return int
144      */
145     @Override
146     public int getPropertyIndex() {
147         if (propertyIndex == UNSPECIFIED_PROPERTY) {
148             final String[] names = getPropertyNames();
149             for (int i = 0; i < names.length; i++) {
150                 if (names[i].equals(name)) {
151                     propertyIndex = i;
152                     name = null;
153                     break;
154                 }
155             }
156         }
157         return super.getPropertyIndex();
158     }
159 
160     /**
161      * Returns the name of the currently selected property or "*" if none has been selected.
162      *
163      * @return String
164      */
165     @Override
166     public String getPropertyName() {
167         if (name == null) {
168             final String[] names = getPropertyNames();
169             name = propertyIndex >= 0 && propertyIndex < names.length ? names[propertyIndex] : "*";
170         }
171         return name;
172     }
173 
174     @Override
175     public String[] getPropertyNames() {
176         /* @todo do something about the sorting - LIKE WHAT? - MJB */
177         if (names == null) {
178             final DynaClass dynaClass = dynaBean.getDynaClass();
179             final DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
180             final ArrayList<String> properties = new ArrayList<>(dynaProperties.length);
181             for (final DynaProperty element : dynaProperties) {
182                 final String name = element.getName();
183                 if (!CLASS.equals(name)) {
184                     properties.add(name);
185                 }
186             }
187             names = properties.toArray(new String[properties.size()]);
188             Arrays.sort(names);
189         }
190         return names;
191     }
192 
193     /**
194      * Returns true if the bean has the currently selected property.
195      *
196      * @return boolean
197      */
198     @Override
199     protected boolean isActualProperty() {
200         final DynaClass dynaClass = dynaBean.getDynaClass();
201         return dynaClass.getDynaProperty(getPropertyName()) != null;
202     }
203 
204     /**
205      * This type of node is auxiliary.
206      *
207      * @return true
208      */
209     @Override
210     public boolean isContainer() {
211         return true;
212     }
213 
214     /**
215      * Tests whether the property referenced is an indexed property.
216      *
217      * @return boolean
218      */
219     protected boolean isIndexedProperty() {
220         final DynaClass dynaClass = dynaBean.getDynaClass();
221         final DynaProperty property = dynaClass.getDynaProperty(name);
222         return property.isIndexed();
223     }
224 
225     @Override
226     public void remove() {
227         if (index == WHOLE_COLLECTION) {
228             dynaBean.set(getPropertyName(), null);
229         } else if (isIndexedProperty()) {
230             dynaBean.set(getPropertyName(), index, null);
231         } else if (isCollection()) {
232             final Object collection = ValueUtils.remove(getBaseValue(), index);
233             dynaBean.set(getPropertyName(), collection);
234         } else if (index == 0) {
235             dynaBean.set(getPropertyName(), null);
236         }
237     }
238 
239     /**
240      * Index a property by its index in the list of all properties sorted alphabetically.
241      *
242      * @param index to set
243      */
244     @Override
245     public void setPropertyIndex(final int index) {
246         if (propertyIndex != index) {
247             super.setPropertyIndex(index);
248             name = null;
249         }
250     }
251 
252     /**
253      * Select a property by name.
254      *
255      * @param propertyName to select
256      */
257     @Override
258     public void setPropertyName(final String propertyName) {
259         setPropertyIndex(UNSPECIFIED_PROPERTY);
260         this.name = propertyName;
261     }
262 
263     /**
264      * Sets an indexed value.
265      *
266      * @param index to change
267      * @param value to set
268      */
269     private void setValue(final int index, final Object value) {
270         if (index == WHOLE_COLLECTION) {
271             dynaBean.set(getPropertyName(), convert(value, false));
272         } else if (isIndexedProperty()) {
273             dynaBean.set(getPropertyName(), index, convert(value, true));
274         } else {
275             final Object baseValue = dynaBean.get(getPropertyName());
276             ValueUtils.setValue(baseValue, index, value);
277         }
278     }
279 
280     /**
281      * 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
282      * property.
283      *
284      * @param value to set
285      */
286     @Override
287     public void setValue(final Object value) {
288         setValue(index, value);
289     }
290 }