View Javadoc
1   /*
2    * Copyright 2004-2025 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 = {};
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    @Override
81    public Class getPropertyTypeForSetter(Object object, String name) {
82      Class type = object.getClass();
83  
84      if (object instanceof Class) {
85        type = getClassPropertyTypeForSetter((Class) object, name);
86      } else if (object instanceof Map) {
87        Map map = (Map) object;
88        Object value = map.get(name);
89        if (value == null) {
90          type = Object.class;
91        } else {
92          type = value.getClass();
93        }
94      } else 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     return type;
105   }
106 
107   /**
108    * Returns the class that the getter will return when reading a property value.
109    *
110    * @param object
111    *          The bean to check
112    * @param name
113    *          The name of the property
114    *
115    * @return The type of the property
116    */
117   @Override
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 if (name.indexOf('.') > -1) {
132       StringTokenizer parser = new StringTokenizer(name, ".");
133       while (parser.hasMoreTokens()) {
134         name = parser.nextToken();
135         type = ClassInfo.getInstance(type).getGetterType(name);
136       }
137     } else {
138       type = ClassInfo.getInstance(type).getGetterType(name);
139     }
140 
141     return type;
142   }
143 
144   /**
145    * Returns the class that the getter will return when reading a property value.
146    *
147    * @param type
148    *          The class to check
149    * @param name
150    *          The name of the property
151    *
152    * @return The type of the property
153    */
154   private Class getClassPropertyTypeForGetter(Class type, String name) {
155 
156     if (name.indexOf('.') > -1) {
157       StringTokenizer parser = new StringTokenizer(name, ".");
158       while (parser.hasMoreTokens()) {
159         name = parser.nextToken();
160         type = ClassInfo.getInstance(type).getGetterType(name);
161       }
162     } else {
163       type = ClassInfo.getInstance(type).getGetterType(name);
164     }
165 
166     return type;
167   }
168 
169   /**
170    * Returns the class that the setter expects to receive as a parameter when setting a property value.
171    *
172    * @param type
173    *          The class to check
174    * @param name
175    *          The name of the property
176    *
177    * @return The type of the property
178    */
179   private Class getClassPropertyTypeForSetter(Class type, String name) {
180 
181     if (name.indexOf('.') > -1) {
182       StringTokenizer parser = new StringTokenizer(name, ".");
183       while (parser.hasMoreTokens()) {
184         name = parser.nextToken();
185         type = ClassInfo.getInstance(type).getSetterType(name);
186       }
187     } else {
188       type = ClassInfo.getInstance(type).getSetterType(name);
189     }
190 
191     return type;
192   }
193 
194   /**
195    * Gets an Object property from a bean
196    *
197    * @param object
198    *          The bean
199    * @param name
200    *          The property name
201    *
202    * @return The property value (as an Object)
203    */
204   @Override
205   public Object getObject(Object object, String name) {
206     if (name.indexOf('.') <= -1) {
207       return getProperty(object, name);
208     }
209     StringTokenizer parser = new StringTokenizer(name, ".");
210     Object value = object;
211     while (parser.hasMoreTokens()) {
212       value = getProperty(value, parser.nextToken());
213 
214       if (value == null) {
215         break;
216       }
217 
218     }
219     return value;
220   }
221 
222   /**
223    * Sets the value of a bean property to an Object
224    *
225    * @param object
226    *          The bean to change
227    * @param name
228    *          The name of the property to set
229    * @param value
230    *          The new value to set
231    */
232   @Override
233   public void setObject(Object object, String name, Object value) {
234     if (name.indexOf('.') > -1) {
235       StringTokenizer parser = new StringTokenizer(name, ".");
236       String property = parser.nextToken();
237       Object child = object;
238       while (parser.hasMoreTokens()) {
239         Class type = getPropertyTypeForSetter(child, property);
240         Object parent = child;
241         child = getProperty(parent, property);
242         if (child == null) {
243           if (value == null) {
244             return; // don't instantiate child path if value is null
245           }
246           try {
247             child = ResultObjectFactoryUtil.createObjectThroughFactory(type);
248             setObject(parent, property, child);
249           } catch (Exception e) {
250             throw new ProbeException("Cannot set value of property '" + name + "' because '" + property
251                 + "' is null and cannot be instantiated on instance of " + type.getName() + ". Cause:" + e.toString(),
252                 e);
253           }
254         }
255         property = parser.nextToken();
256       }
257       setProperty(child, property, value);
258     } else {
259       setProperty(object, name, value);
260     }
261   }
262 
263   /**
264    * Checks to see if a bean has a writable property be a given name
265    *
266    * @param object
267    *          The bean to check
268    * @param propertyName
269    *          The property to check for
270    *
271    * @return True if the property exists and is writable
272    */
273   @Override
274   public boolean hasWritableProperty(Object object, String propertyName) {
275     boolean hasProperty = false;
276     if (object instanceof Map) {
277       hasProperty = true;// ((Map) object).containsKey(propertyName);
278     } else if (propertyName.indexOf('.') > -1) {
279       StringTokenizer parser = new StringTokenizer(propertyName, ".");
280       Class type = object.getClass();
281       while (parser.hasMoreTokens()) {
282         propertyName = parser.nextToken();
283         type = ClassInfo.getInstance(type).getGetterType(propertyName);
284         hasProperty = ClassInfo.getInstance(type).hasWritableProperty(propertyName);
285       }
286     } else {
287       hasProperty = ClassInfo.getInstance(object.getClass()).hasWritableProperty(propertyName);
288     }
289     return hasProperty;
290   }
291 
292   /**
293    * Checks to see if a bean has a readable property be a given name
294    *
295    * @param object
296    *          The bean to check
297    * @param propertyName
298    *          The property to check for
299    *
300    * @return True if the property exists and is readable
301    */
302   @Override
303   public boolean hasReadableProperty(Object object, String propertyName) {
304     boolean hasProperty = false;
305     if (object instanceof Map) {
306       hasProperty = true;// ((Map) object).containsKey(propertyName);
307     } else if (propertyName.indexOf('.') > -1) {
308       StringTokenizer parser = new StringTokenizer(propertyName, ".");
309       Class type = object.getClass();
310       while (parser.hasMoreTokens()) {
311         propertyName = parser.nextToken();
312         type = ClassInfo.getInstance(type).getGetterType(propertyName);
313         hasProperty = ClassInfo.getInstance(type).hasReadableProperty(propertyName);
314       }
315     } else {
316       hasProperty = ClassInfo.getInstance(object.getClass()).hasReadableProperty(propertyName);
317     }
318     return hasProperty;
319   }
320 
321   @Override
322   protected Object getProperty(Object object, String name) {
323     try {
324       Object value = null;
325       if (name.indexOf('[') > -1) {
326         value = getIndexedProperty(object, name);
327       } else if (object instanceof Map) {
328         int index = name.indexOf('.');
329         if (index > -1) {
330           String mapId = name.substring(0, index);
331           value = getProperty(((Map) object).get(mapId), name.substring(index + 1));
332         } else {
333           value = ((Map) object).get(name);
334         }
335 
336       } else {
337         int index = name.indexOf('.');
338         if (index > -1) {
339           String newName = name.substring(0, index);
340           value = getProperty(getObject(object, newName), name.substring(index + 1));
341         } else {
342           ClassInfo classCache = ClassInfo.getInstance(object.getClass());
343           Invoker method = classCache.getGetInvoker(name);
344           if (method == null) {
345             throw new NoSuchMethodException(
346                 "No GET method for property " + name + " on instance of " + object.getClass().getName());
347           }
348           try {
349             value = method.invoke(object, NO_ARGUMENTS);
350           } catch (Throwable t) {
351             throw ClassInfo.unwrapThrowable(t);
352           }
353         }
354 
355       }
356       return value;
357     } catch (ProbeException e) {
358       throw e;
359     } catch (Throwable t) {
360       if (object == null) {
361         throw new ProbeException("Could not get property '" + name + "' from null reference.  Cause: " + t.toString(),
362             t);
363       }
364       throw new ProbeException(
365           "Could not get property '" + name + "' from " + object.getClass().getName() + ".  Cause: " + t.toString(), t);
366     }
367   }
368 
369   @Override
370   protected void setProperty(Object object, String name, Object value) {
371     ClassInfo classCache = ClassInfo.getInstance(object.getClass());
372     try {
373       if (name.indexOf('[') > -1) {
374         setIndexedProperty(object, name, value);
375       } else if (object instanceof Map) {
376         ((Map) object).put(name, value);
377       } else {
378         Invoker method = classCache.getSetInvoker(name);
379         if (method == null) {
380           throw new NoSuchMethodException(
381               "No SET method for property " + name + " on instance of " + object.getClass().getName());
382         }
383         Object[] params = new Object[1];
384         params[0] = value;
385         try {
386           method.invoke(object, params);
387         } catch (Throwable t) {
388           throw ClassInfo.unwrapThrowable(t);
389         }
390       }
391     } catch (ProbeException e) {
392       throw e;
393     } catch (Throwable t) {
394       throw new ProbeException("Could not set property '" + name + "' to value '" + value + "' for "
395           + object.getClass().getName() + ".  Cause: " + t.toString(), t);
396     }
397   }
398 
399 }