View Javadoc

1   /*
2    * Copyright 1999,2004 The Apache Software Foundation.
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    *      http://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  
17  // Contributors:  Georg Lundesgaard
18  package ch.qos.logback.core.util;
19  
20  import java.beans.BeanInfo;
21  import java.beans.IntrospectionException;
22  import java.beans.Introspector;
23  import java.beans.MethodDescriptor;
24  import java.beans.PropertyDescriptor;
25  import java.lang.reflect.Method;
26  
27  import ch.qos.logback.core.spi.ContextAwareBase;
28  
29  
30  /**
31   * General purpose Object property setter. Clients repeatedly invokes
32   * {@link #setProperty setProperty(name,value)} in order to invoke setters on
33   * the Object specified in the constructor. This class relies on the JavaBeans
34   * {@link Introspector} to analyze the given Object Class using reflection.
35   * 
36   * <p>
37   * Usage:
38   * 
39   * <pre>
40   * PropertySetter ps = new PropertySetter(anObject);
41   * ps.set(&quot;name&quot;, &quot;Joe&quot;);
42   * ps.set(&quot;age&quot;, &quot;32&quot;);
43   * ps.set(&quot;isMale&quot;, &quot;true&quot;);
44   * </pre>
45   * 
46   * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and
47   * setMale(true) if such methods exist with those signatures. Otherwise an
48   * {@link IntrospectionException} are thrown.
49   * 
50   * @author Anders Kristensen
51   * @author Ceki Gulcu
52   */
53  public class PropertySetter extends ContextAwareBase {
54    private static final int X_NOT_FOUND = 0;
55    private static final int X_AS_COMPONENT = 1;
56    private static final int X_AS_PROPERTY = 2;
57    
58    private static final Class[] STING_CLASS_PARAMETER = new Class[] {String.class};
59  
60    
61    protected Object obj;
62    protected Class objClass;
63    protected PropertyDescriptor[] propertyDescriptors;
64    protected MethodDescriptor[] methodDescriptors;
65  
66    /**
67     * Create a new PropertySetter for the specified Object. This is done in
68     * preparation for invoking {@link #setProperty} one or more times.
69     * 
70     * @param obj
71     *          the object for which to set properties
72     */
73    public PropertySetter(Object obj) {
74      this.obj = obj;
75      this.objClass = obj.getClass();
76    }
77  
78    /**
79     * Uses JavaBeans {@link Introspector} to computer setters of object to be
80     * configured.
81     */
82    protected void introspect() {
83      try {
84        BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
85        propertyDescriptors = bi.getPropertyDescriptors();
86        methodDescriptors = bi.getMethodDescriptors();
87      } catch (IntrospectionException ex) {
88        addError("Failed to introspect " + obj + ": " + ex.getMessage());
89        propertyDescriptors = new PropertyDescriptor[0];
90        methodDescriptors = new MethodDescriptor[0];
91      }
92    }
93  
94    /**
95     * Set a property on this PropertySetter's Object. If successful, this method
96     * will invoke a setter method on the underlying Object. The setter is the one
97     * for the specified property name and the value is determined partly from the
98     * setter argument type and partly from the value specified in the call to
99     * this method.
100    * 
101    * <p>
102    * If the setter expects a String no conversion is necessary. If it expects an
103    * int, then an attempt is made to convert 'value' to an int using new
104    * Integer(value). If the setter expects a boolean, the conversion is by new
105    * Boolean(value).
106    * 
107    * @param name
108    *          name of the property
109    * @param value
110    *          String value of the property
111    */
112   public void setProperty(String name, String value) {
113     if (value == null) {
114       return;
115     }
116 
117     name = Introspector.decapitalize(name);
118 
119     PropertyDescriptor prop = getPropertyDescriptor(name);
120 
121     if (prop == null) {
122       addWarn("No such property [" + name + "] in " + objClass.getName() + ".");
123     } else {
124       try {
125         setProperty(prop, name, value);
126       } catch (PropertySetterException ex) {
127         addWarn("Failed to set property [" + name + "] to value \"" + value
128             + "\". ", ex);
129       }
130     }
131   }
132 
133   /**
134    * Set the named property given a {@link PropertyDescriptor}.
135    * 
136    * @param prop
137    *          A PropertyDescriptor describing the characteristics of the
138    *          property to set.
139    * @param name
140    *          The named of the property to set.
141    * @param value
142    *          The value of the property.
143    */
144   public void setProperty(PropertyDescriptor prop, String name, String value)
145       throws PropertySetterException {
146     Method setter = prop.getWriteMethod();
147 
148     if (setter == null) {
149       throw new PropertySetterException("No setter for property [" + name
150           + "].");
151     }
152 
153     Class[] paramTypes = setter.getParameterTypes();
154   
155     
156     if (paramTypes.length != 1) {
157       throw new PropertySetterException("#params for setter != 1");
158     }
159     
160     Object arg;
161 
162     try {
163       arg = convertArg(value, paramTypes[0]);
164     } catch (Throwable t) {
165       throw new PropertySetterException("Conversion to type [" + paramTypes[0]
166           + "] failed. ", t);
167     }
168 
169     if (arg == null) {
170       throw new PropertySetterException("Conversion to type [" + paramTypes[0]
171           + "] failed.");
172     }
173 
174     // getLogger().debug("Setting property [{}] to [{}].", name, arg);
175 
176     try {
177       setter.invoke(obj, new Object[] { arg });
178     } catch (Exception ex) {
179       throw new PropertySetterException(ex);
180     }
181   }
182 
183   public ContainmentType canContainComponent(String name) {
184     String cName = capitalizeFirstLetter(name);
185 
186     Method addMethod = getMethod("add" + cName);
187 
188     if (addMethod != null) {
189       int type = computeContainmentTpye(addMethod);
190       switch (type) {
191       case X_NOT_FOUND:
192         return ContainmentType.NOT_FOUND;
193       case X_AS_PROPERTY:
194         return ContainmentType.AS_PROPERTY_COLLECTION;
195       case X_AS_COMPONENT:
196         return ContainmentType.AS_COMPONENT_COLLECTION;
197       }
198     }
199 
200     String dName = Introspector.decapitalize(name);
201 
202     PropertyDescriptor propertyDescriptor = getPropertyDescriptor(dName);
203 
204     if (propertyDescriptor != null) {
205       Method setterMethod = propertyDescriptor.getWriteMethod();
206       if (setterMethod != null) {
207         // getLogger().debug(
208         // "Found setter method for property [{}] in class {}", name,
209         // objClass.getName());
210         int type = computeContainmentTpye(setterMethod);
211         // getLogger().debug(
212         // "Found add {} method in class {}", cName, objClass.getName());
213         switch (type) {
214         case X_NOT_FOUND:
215           return ContainmentType.NOT_FOUND;
216         case X_AS_PROPERTY:
217           return ContainmentType.AS_SINGLE_PROPERTY;
218         case X_AS_COMPONENT:
219           return ContainmentType.AS_SINGLE_COMPONENT;
220         }
221       }
222     }
223 
224     // we have failed
225     return ContainmentType.NOT_FOUND;
226   }
227 
228   int computeContainmentTpye(Method setterMethod) {
229     Class[] classArray = setterMethod.getParameterTypes();
230     if (classArray.length != 1) {
231       return X_NOT_FOUND;
232     } else {
233       Class clazz = classArray[0];
234       Package p = clazz.getPackage();
235       if (clazz.isPrimitive()) {
236         return X_AS_PROPERTY;
237       } else if (p != null && "java.lang".equals(p.getName())) {
238         return X_AS_PROPERTY;
239       } else if (Duration.class.isAssignableFrom(clazz)) {
240         return X_AS_PROPERTY;
241       } else if (FileSize.class.isAssignableFrom(clazz)) {
242         return X_AS_PROPERTY;
243       } else if (clazz.isEnum()){
244         return X_AS_PROPERTY;
245       } else {
246         return X_AS_COMPONENT;
247       }
248     }
249   }
250 
251   public Class getObjClass() {
252     return objClass;
253   }
254 
255   @SuppressWarnings("unchecked")
256   public void addComponent(String name, Object childComponent) {
257     Class ccc = childComponent.getClass();
258     name = capitalizeFirstLetter(name);
259 
260     Method method = getMethod("add" + name);
261 
262     // first let us use the addXXX method
263     if (method != null) {
264       Class[] params = method.getParameterTypes();
265 
266       if (params.length == 1) {
267         if (params[0].isAssignableFrom(childComponent.getClass())) {
268           try {
269             method.invoke(this.obj, new Object[] { childComponent });
270           } catch (Exception e) {
271             addError("Could not invoke method " + method.getName()
272                 + " in class " + obj.getClass().getName()
273                 + " with parameter of type " + ccc.getName(), e);
274           }
275         } else {
276           addError("A \"" + ccc.getName()
277               + "\" object is not assignable to a \"" + params[0].getName()
278               + "\" variable.");
279           addError("The class \"" + params[0].getName() + "\" was loaded by ");
280           addError("[" + params[0].getClassLoader()
281               + "] whereas object of type ");
282           addError("\"" + ccc.getName() + "\" was loaded by ["
283               + ccc.getClassLoader() + "].");
284         }
285       }
286     } else {
287       addError("Could not find method [" + "add" + name + "] in class ["
288           + objClass.getName() + "].");
289     }
290   }
291 
292   @SuppressWarnings("unchecked")
293   public void addProperty(String name, String strValue) {
294 
295     if (strValue == null) {
296       return;
297     }
298 
299     name = capitalizeFirstLetter(name);
300     Method adderMethod = getMethod("add" + name);
301 
302     if (adderMethod == null) {
303       addError("No adder for property [" + name + "].");
304       return;
305     }
306 
307     Class[] paramTypes = adderMethod.getParameterTypes();
308     if (paramTypes.length != 1) {
309       addError("#params for setter != 1");
310       return;
311 
312     }
313     Object arg;
314     try {
315       arg = convertArg(strValue, paramTypes[0]);
316     } catch (Throwable t) {
317       addError("Conversion to type [" + paramTypes[0] + "] failed. ", t);
318       return;
319     }
320 
321     if (arg == null) {
322       addError("Conversion to type [" + paramTypes[0] + "] failed.");
323     } else {
324       try {
325         adderMethod.invoke(obj, arg);
326       } catch (Exception ex) {
327         addError("Failed to invoke adder for " + name, ex);
328       }
329     }
330 
331   }
332 
333   public void setComponent(String name, Object childComponent) {
334     String dName = Introspector.decapitalize(name);
335     PropertyDescriptor propertyDescriptor = getPropertyDescriptor(dName);
336 
337     if (propertyDescriptor == null) {
338       addWarn("Could not find PropertyDescriptor for [" + name + "] in "
339           + objClass.getName());
340 
341       return;
342     }
343 
344     Method setter = propertyDescriptor.getWriteMethod();
345 
346     if (setter == null) {
347       addWarn("Not setter method for property [" + name + "] in "
348           + obj.getClass().getName());
349 
350       return;
351     }
352 
353     Class[] paramTypes = setter.getParameterTypes();
354 
355     if (paramTypes.length != 1) {
356       addError("Wrong number of parameters in setter method for property ["
357           + name + "] in " + obj.getClass().getName());
358 
359       return;
360     }
361 
362     try {
363       setter.invoke(obj, new Object[] { childComponent });
364       // getLogger().debug(
365       // "Set child component of type [{}] for [{}].", objClass.getName(),
366       // childComponent.getClass().getName());
367     } catch (Exception e) {
368       addError("Could not set component " + obj + " for parent component "
369           + obj, e);
370     }
371   }
372 
373   private String capitalizeFirstLetter(String name) {
374     return name.substring(0, 1).toUpperCase() + name.substring(1);
375   }
376 
377   /**
378    * Convert <code>val</code> a String parameter to an object of a given type.
379    */
380   protected Object convertArg(String val, Class type) {
381     if (val == null) {
382       return null;
383     }
384 
385     String v = val.trim();
386 
387     if (String.class.isAssignableFrom(type)) {
388       return val;
389     } else if (Integer.TYPE.isAssignableFrom(type)) {
390       return new Integer(v);
391     } else if (Long.TYPE.isAssignableFrom(type)) {
392       return new Long(v);
393     } else if (Float.TYPE.isAssignableFrom(type)) {
394       return new Float(v);
395     } else if (Double.TYPE.isAssignableFrom(type)) {
396       return new Double(v);
397     } else if (Boolean.TYPE.isAssignableFrom(type)) {
398       if ("true".equalsIgnoreCase(v)) {
399         return Boolean.TRUE;
400       } else if ("false".equalsIgnoreCase(v)) {
401         return Boolean.FALSE;
402       }
403     } else if (Duration.class.isAssignableFrom(type)) {
404       return Duration.valueOf(val);
405     } else if (FileSize.class.isAssignableFrom(type)) {
406       return FileSize.valueOf(val);
407     } else if(type.isEnum()) {
408       return convertEnum(val, type);
409     }
410 
411     return null;
412   }
413 
414   protected Object convertEnum(String val, Class type) {
415     try {
416       Method m = type.getMethod("valueOf", STING_CLASS_PARAMETER);
417       return m.invoke(null, val);
418     } catch (Exception e) {
419       addError("Failed to convert value ["+val+"] to enum ["+type.getName()+"]", e);
420     }
421     return null;
422   }
423   
424   protected Method getMethod(String methodName) {
425     if (methodDescriptors == null) {
426       introspect();
427     }
428 
429     for (int i = 0; i < methodDescriptors.length; i++) {
430       if (methodName.equals(methodDescriptors[i].getName())) {
431         return methodDescriptors[i].getMethod();
432       }
433     }
434 
435     return null;
436   }
437 
438   protected PropertyDescriptor getPropertyDescriptor(String name) {
439     if (propertyDescriptors == null) {
440       introspect();
441     }
442 
443     for (int i = 0; i < propertyDescriptors.length; i++) {
444       // System.out.println("Comparing " + name + " against "
445       // + propertyDescriptors[i].getName());
446       if (name.equals(propertyDescriptors[i].getName())) {
447         // System.out.println("matched");
448         return propertyDescriptors[i];
449       }
450     }
451 
452     return null;
453   }
454 
455   public Object getObj() {
456     return obj;
457   }
458 }