1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  // Contributors:  Georg Lundesgaard
15  package ch.qos.logback.core.joran.util;
16  
17  import ch.qos.logback.core.Context;
18  import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
19  import ch.qos.logback.core.joran.util.beans.BeanDescription;
20  import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
21  import ch.qos.logback.core.spi.ContextAwareBase;
22  import ch.qos.logback.core.util.AggregationType;
23  import ch.qos.logback.core.util.PropertySetterException;
24  import ch.qos.logback.core.util.StringUtil;
25  
26  import java.lang.reflect.Method;
27  
28  /**
29   * General purpose Object property setter. Clients repeatedly invokes
30   * {@link #setProperty setProperty(name,value)} in order to invoke setters on
31   * the Object specified in the constructor. This class relies on reflection to
32   * analyze the given Object Class.
33   *
34   * <p>
35   * Usage:
36   *
37   * <pre>
38   * PropertySetter ps = new PropertySetter(anObject);
39   * ps.set(&quot;name&quot;, &quot;Joe&quot;);
40   * ps.set(&quot;age&quot;, &quot;32&quot;);
41   * ps.set(&quot;isMale&quot;, &quot;true&quot;);
42   * </pre>
43   * <p>
44   * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and
45   * setMale(true) if such methods exist with those signatures. Otherwise an
46   * {@link PropertySetterException} is thrown.
47   *
48   * @author Anders Kristensen
49   * @author Ceki Gulcu
50   */
51  public class PropertySetter extends ContextAwareBase {
52  
53      protected final Object obj;
54      protected final Class<?> objClass;
55      protected final BeanDescription beanDescription;
56      protected final AggregationAssessor aggregationAssessor;
57  
58      /**
59       * Create a new PropertySetter for the specified Object. This is done in
60       * preparation for invoking {@link #setProperty} one or more times.
61       *
62       * @param obj the object for which to set properties
63       */
64      public PropertySetter(BeanDescriptionCache beanDescriptionCache, Object obj) {
65          this.obj = obj;
66          this.objClass = obj.getClass();
67          this.beanDescription = beanDescriptionCache.getBeanDescription(objClass);
68          this.aggregationAssessor = new AggregationAssessor(beanDescriptionCache, this.objClass);
69      }
70  
71      @Override
72      public void setContext(Context context) {
73          super.setContext(context);
74          aggregationAssessor.setContext(context);
75      }
76  
77  
78      /**
79       * Set a property on this PropertySetter's Object. If successful, this method
80       * will invoke a setter method on the underlying Object. The setter is the one
81       * for the specified property name and the value is determined partly from the
82       * setter argument type and partly from the value specified in the call to this
83       * method.
84       *
85       * <p>
86       * If the setter expects a String no conversion is necessary. If it expects an
87       * int, then an attempt is made to convert 'value' to an int using new
88       * Integer(value). If the setter expects a boolean, the conversion is by new
89       * Boolean(value).
90       *
91       * @param name  name of the property
92       * @param value String value of the property
93       */
94      public void setProperty(String name, String value) {
95          if (value == null) {
96              return;
97          }
98          Method setter = aggregationAssessor.findSetterMethod(name);
99          if (setter == null) {
100             addWarn("No setter for property [" + name + "] in " + objClass.getName() + ".");
101         } else {
102             try {
103                 setProperty(setter, value);
104             } catch (PropertySetterException ex) {
105                 addWarn("Failed to set property [" + name + "] to value \"" + value + "\". ", ex);
106             }
107         }
108     }
109 
110     /**
111      * Set the named property using a {@link Method setter}.
112      *
113      * @param setter A Method describing the characteristics of the
114      *               property to set.
115      * @param value  The value of the property.
116      */
117     private void setProperty(Method setter, String value) throws PropertySetterException {
118         Class<?>[] paramTypes = setter.getParameterTypes();
119 
120         Object arg;
121 
122         try {
123             arg = StringToObjectConverter.convertArg(this, value, paramTypes[0]);
124         } catch (Throwable t) {
125             throw new PropertySetterException("Conversion to type [" + paramTypes[0] + "] failed. ", t);
126         }
127 
128         if (arg == null) {
129             throw new PropertySetterException("Conversion to type [" + paramTypes[0] + "] failed.");
130         }
131         try {
132             setter.invoke(obj, arg);
133         } catch (Exception ex) {
134             throw new PropertySetterException(ex);
135         }
136     }
137 
138     public AggregationType computeAggregationType(String name) {
139         return this.aggregationAssessor.computeAggregationType(name);
140     }
141 
142 
143     public Class<?> getObjClass() {
144         return objClass;
145     }
146 
147     public void addComplexProperty(String name, Object complexProperty) {
148         Method adderMethod = aggregationAssessor.findAdderMethod(name);
149         // first let us use the addXXX method
150         if (adderMethod != null) {
151             Class<?>[] paramTypes = adderMethod.getParameterTypes();
152             if (!isSanityCheckSuccessful(name, adderMethod, paramTypes, complexProperty)) {
153                 return;
154             }
155             invokeMethodWithSingleParameterOnThisObject(adderMethod, complexProperty);
156         } else {
157             addError("Could not find method [" + "add" + name + "] in class [" + objClass.getName() + "].");
158         }
159     }
160 
161     void invokeMethodWithSingleParameterOnThisObject(Method method, Object parameter) {
162         Class<?> ccc = parameter.getClass();
163         try {
164             method.invoke(this.obj, parameter);
165         } catch (Exception e) {
166             addError("Could not invoke method " + method.getName() + " in class " + obj.getClass().getName()
167                     + " with parameter of type " + ccc.getName(), e);
168         }
169     }
170 
171     public void addBasicProperty(String name, String strValue) {
172 
173         if (strValue == null) {
174             return;
175         }
176 
177         name = StringUtil.capitalizeFirstLetter(name);
178         Method adderMethod = aggregationAssessor.findAdderMethod(name);
179 
180         if (adderMethod == null) {
181             addError("No adder for property [" + name + "].");
182             return;
183         }
184 
185         Class<?>[] paramTypes = adderMethod.getParameterTypes();
186         isSanityCheckSuccessful(name, adderMethod, paramTypes, strValue);
187 
188         Object arg;
189         try {
190             arg = StringToObjectConverter.convertArg(this, strValue, paramTypes[0]);
191         } catch (Throwable t) {
192             addError("Conversion to type [" + paramTypes[0] + "] failed. ", t);
193             return;
194         }
195         if (arg != null) {
196             invokeMethodWithSingleParameterOnThisObject(adderMethod, arg);
197         }
198     }
199 
200     public void setComplexProperty(String name, Object complexProperty) {
201         Method setter = aggregationAssessor.findSetterMethod(name);
202 
203         if (setter == null) {
204             addWarn("Not setter method for property [" + name + "] in " + obj.getClass().getName());
205 
206             return;
207         }
208 
209         Class<?>[] paramTypes = setter.getParameterTypes();
210 
211         if (!isSanityCheckSuccessful(name, setter, paramTypes, complexProperty)) {
212             return;
213         }
214         try {
215             invokeMethodWithSingleParameterOnThisObject(setter, complexProperty);
216 
217         } catch (Exception e) {
218             addError("Could not set component " + obj + " for parent component " + obj, e);
219         }
220     }
221 
222     private boolean isSanityCheckSuccessful(String name, Method method, Class<?>[] params, Object complexProperty) {
223         Class<?> ccc = complexProperty.getClass();
224         if (params.length != 1) {
225             addError("Wrong number of parameters in setter method for property [" + name + "] in "
226                     + obj.getClass().getName());
227 
228             return false;
229         }
230 
231         if (!params[0].isAssignableFrom(complexProperty.getClass())) {
232             addError("A \"" + ccc.getName() + "\" object is not assignable to a \"" + params[0].getName()
233                     + "\" variable.");
234             addError("The class \"" + params[0].getName() + "\" was loaded by ");
235             addError("[" + params[0].getClassLoader() + "] whereas object of type ");
236             addError("\"" + ccc.getName() + "\" was loaded by [" + ccc.getClassLoader() + "].");
237             return false;
238         }
239 
240         return true;
241     }
242 
243     public Object getObj() {
244         return obj;
245     }
246 
247 
248     public Class<?> getClassNameViaImplicitRules(String name, AggregationType aggregationType,
249                                                  DefaultNestedComponentRegistry registry) {
250         return aggregationAssessor.getClassNameViaImplicitRules(name, aggregationType, registry);
251     }
252 
253     public Class<?> getTypeForComplexProperty(String nestedElementTagName, AggregationType aggregationType) {
254 
255         Method aMethod = null;
256         switch (aggregationType) {
257             case AS_COMPLEX_PROPERTY:
258                 aMethod = aggregationAssessor.findSetterMethod(nestedElementTagName);
259                 break;
260             case AS_COMPLEX_PROPERTY_COLLECTION:
261                 aMethod = aggregationAssessor.findAdderMethod(nestedElementTagName);
262         }
263 
264 
265         checkParameterCount(aMethod, nestedElementTagName);
266 
267         Class<?>[] paramTypes = aMethod.getParameterTypes();
268         return paramTypes[0];
269 
270     }
271 
272     private void checkParameterCount(Method aMethod, String nestedElementTagName) {
273         if(aMethod == null) {
274             String msg = "Could not find method for property [" + nestedElementTagName + "].";
275             addError(msg);
276             throw new IllegalStateException(msg);
277         }
278         int parameterCount = aMethod.getParameterCount();
279         if (parameterCount != 1) {
280             String msg = "Expected ["+aMethod.getName()+"] for property [" + nestedElementTagName + "] to have exactly one parameter.";
281             addError(msg);
282             throw new IllegalStateException(msg);
283         }
284     }
285 }