View Javadoc
1   /*
2    * Copyright 2004-2022 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.ibatis.common.beans;
17  
18  import com.ibatis.sqlmap.engine.mapping.result.ResultObjectFactoryUtil;
19  
20  import java.util.Map;
21  import java.util.StringTokenizer;
22  
23  /**
24   * StaticBeanProbe provides methods that allow simple, reflective access to JavaBeans style properties. Methods are
25   * provided for all simple types as well as object types.
26   * <p>
27   * Examples:
28   * <p>
29   * StaticBeanProbe.setObject(object, propertyName, value);
30   * <p>
31   * Object value = StaticBeanProbe.getObject(object, propertyName);
32   */
33  public class ComplexBeanProbe extends BaseProbe {
34  
35    /** The Constant NO_ARGUMENTS. */
36    private static final Object[] NO_ARGUMENTS = new Object[0];
37  
38    /**
39     * Instantiates a new complex bean probe.
40     */
41    protected ComplexBeanProbe() {
42    }
43  
44    /**
45     * Returns an array of the readable properties exposed by a bean
46     *
47     * @param object
48     *          The bean
49     *
50     * @return The properties
51     */
52    @Override
53    public String[] getReadablePropertyNames(Object object) {
54      return ClassInfo.getInstance(object.getClass()).getReadablePropertyNames();
55    }
56  
57    /**
58     * Returns an array of the writeable properties exposed by a bean
59     *
60     * @param object
61     *          The bean
62     *
63     * @return The properties
64     */
65    @Override
66    public String[] getWriteablePropertyNames(Object object) {
67      return ClassInfo.getInstance(object.getClass()).getWriteablePropertyNames();
68    }
69  
70    /**
71     * Returns the class that the setter expects to receive as a parameter when setting a property value.
72     *
73     * @param object
74     *          The bean to check
75     * @param name
76     *          The name of the property
77     *
78     * @return The type of the property
79     */
80    public Class getPropertyTypeForSetter(Object object, String name) {
81      Class type = object.getClass();
82  
83      if (object instanceof Class) {
84        type = getClassPropertyTypeForSetter((Class) object, name);
85      } else if (object instanceof Map) {
86        Map map = (Map) object;
87        Object value = map.get(name);
88        if (value == null) {
89          type = Object.class;
90        } else {
91          type = value.getClass();
92        }
93      } else {
94        if (name.indexOf('.') > -1) {
95          StringTokenizer parser = new StringTokenizer(name, ".");
96          while (parser.hasMoreTokens()) {
97            name = parser.nextToken();
98            type = ClassInfo.getInstance(type).getSetterType(name);
99          }
100       } else {
101         type = ClassInfo.getInstance(type).getSetterType(name);
102       }
103     }
104 
105     return type;
106   }
107 
108   /**
109    * Returns the class that the getter will return when reading a property value.
110    *
111    * @param object
112    *          The bean to check
113    * @param name
114    *          The name of the property
115    *
116    * @return The type of the property
117    */
118   public Class getPropertyTypeForGetter(Object object, String name) {
119     Class type = object.getClass();
120 
121     if (object instanceof Class) {
122       type = getClassPropertyTypeForGetter((Class) object, name);
123     } else if (object instanceof Map) {
124       Map map = (Map) object;
125       Object value = map.get(name);
126       if (value == null) {
127         type = Object.class;
128       } else {
129         type = value.getClass();
130       }
131     } else {
132       if (name.indexOf('.') > -1) {
133         StringTokenizer parser = new StringTokenizer(name, ".");
134         while (parser.hasMoreTokens()) {
135           name = parser.nextToken();
136           type = ClassInfo.getInstance(type).getGetterType(name);
137         }
138       } else {
139         type = ClassInfo.getInstance(type).getGetterType(name);
140       }
141     }
142 
143     return type;
144   }
145 
146   /**
147    * Returns the class that the getter will return when reading a property value.
148    *
149    * @param type
150    *          The class to check
151    * @param name
152    *          The name of the property
153    *
154    * @return The type of the property
155    */
156   private Class getClassPropertyTypeForGetter(Class type, String name) {
157 
158     if (name.indexOf('.') > -1) {
159       StringTokenizer parser = new StringTokenizer(name, ".");
160       while (parser.hasMoreTokens()) {
161         name = parser.nextToken();
162         type = ClassInfo.getInstance(type).getGetterType(name);
163       }
164     } else {
165       type = ClassInfo.getInstance(type).getGetterType(name);
166     }
167 
168     return type;
169   }
170 
171   /**
172    * Returns the class that the setter expects to receive as a parameter when setting a property value.
173    *
174    * @param type
175    *          The class to check
176    * @param name
177    *          The name of the property
178    *
179    * @return The type of the property
180    */
181   private Class getClassPropertyTypeForSetter(Class type, String name) {
182 
183     if (name.indexOf('.') > -1) {
184       StringTokenizer parser = new StringTokenizer(name, ".");
185       while (parser.hasMoreTokens()) {
186         name = parser.nextToken();
187         type = ClassInfo.getInstance(type).getSetterType(name);
188       }
189     } else {
190       type = ClassInfo.getInstance(type).getSetterType(name);
191     }
192 
193     return type;
194   }
195 
196   /**
197    * Gets an Object property from a bean
198    *
199    * @param object
200    *          The bean
201    * @param name
202    *          The property name
203    *
204    * @return The property value (as an Object)
205    */
206   public Object getObject(Object object, String name) {
207     if (name.indexOf('.') > -1) {
208       StringTokenizer parser = new StringTokenizer(name, ".");
209       Object value = object;
210       while (parser.hasMoreTokens()) {
211         value = getProperty(value, parser.nextToken());
212 
213         if (value == null) {
214           break;
215         }
216 
217       }
218       return value;
219     } else {
220       return getProperty(object, name);
221     }
222   }
223 
224   /**
225    * Sets the value of a bean property to an Object
226    *
227    * @param object
228    *          The bean to change
229    * @param name
230    *          The name of the property to set
231    * @param value
232    *          The new value to set
233    */
234   public void setObject(Object object, String name, Object value) {
235     if (name.indexOf('.') > -1) {
236       StringTokenizer parser = new StringTokenizer(name, ".");
237       String property = parser.nextToken();
238       Object child = object;
239       while (parser.hasMoreTokens()) {
240         Class type = getPropertyTypeForSetter(child, property);
241         Object parent = child;
242         child = getProperty(parent, property);
243         if (child == null) {
244           if (value == null) {
245             return; // don't instantiate child path if value is null
246           } else {
247             try {
248               child = ResultObjectFactoryUtil.createObjectThroughFactory(type);
249               setObject(parent, property, child);
250             } catch (Exception e) {
251               throw new ProbeException("Cannot set value of property '" + name + "' because '" + property
252                   + "' is null and cannot be instantiated on instance of " + type.getName() + ". Cause:" + e.toString(),
253                   e);
254             }
255           }
256         }
257         property = parser.nextToken();
258       }
259       setProperty(child, property, value);
260     } else {
261       setProperty(object, name, value);
262     }
263   }
264 
265   /**
266    * Checks to see if a bean has a writable property be a given name
267    *
268    * @param object
269    *          The bean to check
270    * @param propertyName
271    *          The property to check for
272    *
273    * @return True if the property exists and is writable
274    */
275   public boolean hasWritableProperty(Object object, String propertyName) {
276     boolean hasProperty = false;
277     if (object instanceof Map) {
278       hasProperty = true;// ((Map) object).containsKey(propertyName);
279     } else {
280       if (propertyName.indexOf('.') > -1) {
281         StringTokenizer parser = new StringTokenizer(propertyName, ".");
282         Class type = object.getClass();
283         while (parser.hasMoreTokens()) {
284           propertyName = parser.nextToken();
285           type = ClassInfo.getInstance(type).getGetterType(propertyName);
286           hasProperty = ClassInfo.getInstance(type).hasWritableProperty(propertyName);
287         }
288       } else {
289         hasProperty = ClassInfo.getInstance(object.getClass()).hasWritableProperty(propertyName);
290       }
291     }
292     return hasProperty;
293   }
294 
295   /**
296    * Checks to see if a bean has a readable property be a given name
297    *
298    * @param object
299    *          The bean to check
300    * @param propertyName
301    *          The property to check for
302    *
303    * @return True if the property exists and is readable
304    */
305   public boolean hasReadableProperty(Object object, String propertyName) {
306     boolean hasProperty = false;
307     if (object instanceof Map) {
308       hasProperty = true;// ((Map) object).containsKey(propertyName);
309     } else {
310       if (propertyName.indexOf('.') > -1) {
311         StringTokenizer parser = new StringTokenizer(propertyName, ".");
312         Class type = object.getClass();
313         while (parser.hasMoreTokens()) {
314           propertyName = parser.nextToken();
315           type = ClassInfo.getInstance(type).getGetterType(propertyName);
316           hasProperty = ClassInfo.getInstance(type).hasReadableProperty(propertyName);
317         }
318       } else {
319         hasProperty = ClassInfo.getInstance(object.getClass()).hasReadableProperty(propertyName);
320       }
321     }
322     return hasProperty;
323   }
324 
325   @Override
326   protected Object getProperty(Object object, String name) {
327     try {
328       Object value = null;
329       if (name.indexOf('[') > -1) {
330         value = getIndexedProperty(object, name);
331       } else {
332         if (object instanceof Map) {
333           int index = name.indexOf('.');
334           if (index > -1) {
335             String mapId = name.substring(0, index);
336             value = getProperty(((Map) object).get(mapId), name.substring(index + 1));
337           } else {
338             value = ((Map) object).get(name);
339           }
340 
341         } else {
342           int index = name.indexOf('.');
343           if (index > -1) {
344             String newName = name.substring(0, index);
345             value = getProperty(getObject(object, newName), name.substring(index + 1));
346           } else {
347             ClassInfo classCache = ClassInfo.getInstance(object.getClass());
348             Invoker method = classCache.getGetInvoker(name);
349             if (method == null) {
350               throw new NoSuchMethodException(
351                   "No GET method for property " + name + " on instance of " + object.getClass().getName());
352             }
353             try {
354               value = method.invoke(object, NO_ARGUMENTS);
355             } catch (Throwable t) {
356               throw ClassInfo.unwrapThrowable(t);
357             }
358           }
359 
360         }
361       }
362       return value;
363     } catch (ProbeException e) {
364       throw e;
365     } catch (Throwable t) {
366       if (object == null) {
367         throw new ProbeException("Could not get property '" + name + "' from null reference.  Cause: " + t.toString(),
368             t);
369       } else {
370         throw new ProbeException(
371             "Could not get property '" + name + "' from " + object.getClass().getName() + ".  Cause: " + t.toString(),
372             t);
373       }
374     }
375   }
376 
377   @Override
378   protected void setProperty(Object object, String name, Object value) {
379     ClassInfo classCache = ClassInfo.getInstance(object.getClass());
380     try {
381       if (name.indexOf('[') > -1) {
382         setIndexedProperty(object, name, value);
383       } else {
384         if (object instanceof Map) {
385           ((Map) object).put(name, value);
386         } else {
387           Invoker method = classCache.getSetInvoker(name);
388           if (method == null) {
389             throw new NoSuchMethodException(
390                 "No SET method for property " + name + " on instance of " + object.getClass().getName());
391           }
392           Object[] params = new Object[1];
393           params[0] = value;
394           try {
395             method.invoke(object, params);
396           } catch (Throwable t) {
397             throw ClassInfo.unwrapThrowable(t);
398           }
399         }
400       }
401     } catch (ProbeException e) {
402       throw e;
403     } catch (Throwable t) {
404       throw new ProbeException("Could not set property '" + name + "' to value '" + value + "' for "
405           + object.getClass().getName() + ".  Cause: " + t.toString(), t);
406     }
407   }
408 
409 }