001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014// Contributors:  Georg Lundesgaard
015package ch.qos.logback.core.joran.util;
016
017import ch.qos.logback.core.Context;
018import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
019import ch.qos.logback.core.joran.util.beans.BeanDescription;
020import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
021import ch.qos.logback.core.spi.ContextAwareBase;
022import ch.qos.logback.core.util.AggregationType;
023import ch.qos.logback.core.util.PropertySetterException;
024import ch.qos.logback.core.util.StringUtil;
025
026import java.lang.reflect.Method;
027
028/**
029 * General purpose Object property setter. Clients repeatedly invokes
030 * {@link #setProperty setProperty(name,value)} in order to invoke setters on
031 * the Object specified in the constructor. This class relies on reflection to
032 * analyze the given Object Class.
033 *
034 * <p>
035 * Usage:
036 *
037 * <pre>
038 * PropertySetter ps = new PropertySetter(anObject);
039 * ps.set(&quot;name&quot;, &quot;Joe&quot;);
040 * ps.set(&quot;age&quot;, &quot;32&quot;);
041 * ps.set(&quot;isMale&quot;, &quot;true&quot;);
042 * </pre>
043 *
044 * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and
045 * setMale(true) if such methods exist with those signatures. Otherwise an
046 * {@link PropertySetterException} is thrown.
047 *
048 * @author Anders Kristensen
049 * @author Ceki Gulcu
050 */
051public class PropertySetter extends ContextAwareBase {
052
053    protected final Object obj;
054    protected final Class<?> objClass;
055    protected final BeanDescription beanDescription;
056    protected final AggregationAssessor aggregationAssessor;
057
058    /**
059     * Create a new PropertySetter for the specified Object. This is done in
060     * preparation for invoking {@link #setProperty} one or more times.
061     *
062     * @param obj the object for which to set properties
063     */
064    public PropertySetter(BeanDescriptionCache beanDescriptionCache, Object obj) {
065        this.obj = obj;
066        this.objClass = obj.getClass();
067        this.beanDescription = beanDescriptionCache.getBeanDescription(objClass);
068        this.aggregationAssessor = new AggregationAssessor(beanDescriptionCache, this.objClass);
069    }
070
071    @Override
072    public void setContext(Context context) {
073        super.setContext(context);
074        aggregationAssessor.setContext(context);
075    }
076
077
078    /**
079     * Set a property on this PropertySetter's Object. If successful, this method
080     * will invoke a setter method on the underlying Object. The setter is the one
081     * for the specified property name and the value is determined partly from the
082     * setter argument type and partly from the value specified in the call to this
083     * method.
084     *
085     * <p>
086     * If the setter expects a String no conversion is necessary. If it expects an
087     * int, then an attempt is made to convert 'value' to an int using new
088     * Integer(value). If the setter expects a boolean, the conversion is by new
089     * Boolean(value).
090     *
091     * @param name  name of the property
092     * @param value String value of the property
093     */
094    public void setProperty(String name, String value) {
095        if (value == null) {
096            return;
097        }
098        Method setter = aggregationAssessor.findSetterMethod(name);
099        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//    private Method findAdderMethod(String name) {
143//        String propertyName = BeanUtil.toLowerCamelCase(name);
144//        return beanDescription.getAdder(propertyName);
145//    }
146//
147//    private Method findSetterMethod(String name) {
148//        String propertyName = BeanUtil.toLowerCamelCase(name);
149//        return beanDescription.getSetter(propertyName);
150//    }
151
152//    private Class<?> getParameterClassForMethod(Method method) {
153//        if (method == null) {
154//            return null;
155//        }
156//        Class<?>[] classArray = method.getParameterTypes();
157//        if (classArray.length != 1) {
158//            return null;
159//        } else {
160//            return classArray[0];
161//        }
162//    }
163
164//    private AggregationType computeRawAggregationType(Method method) {
165//        Class<?> parameterClass = getParameterClassForMethod(method);
166//        if (parameterClass == null) {
167//            return AggregationType.NOT_FOUND;
168//        }
169//        if (StringToObjectConverter.canBeBuiltFromSimpleString(parameterClass)) {
170//            return AggregationType.AS_BASIC_PROPERTY;
171//        } else {
172//            return AggregationType.AS_COMPLEX_PROPERTY;
173//        }
174//    }
175
176
177
178    public Class<?> getObjClass() {
179        return objClass;
180    }
181
182    public void addComplexProperty(String name, Object complexProperty) {
183        Method adderMethod = aggregationAssessor.findAdderMethod(name);
184        // first let us use the addXXX method
185        if (adderMethod != null) {
186            Class<?>[] paramTypes = adderMethod.getParameterTypes();
187            if (!isSanityCheckSuccessful(name, adderMethod, paramTypes, complexProperty)) {
188                return;
189            }
190            invokeMethodWithSingleParameterOnThisObject(adderMethod, complexProperty);
191        } else {
192            addError("Could not find method [" + "add" + name + "] in class [" + objClass.getName() + "].");
193        }
194    }
195
196    void invokeMethodWithSingleParameterOnThisObject(Method method, Object parameter) {
197        Class<?> ccc = parameter.getClass();
198        try {
199            method.invoke(this.obj, parameter);
200        } catch (Exception e) {
201            addError("Could not invoke method " + method.getName() + " in class " + obj.getClass().getName()
202                    + " with parameter of type " + ccc.getName(), e);
203        }
204    }
205
206    public void addBasicProperty(String name, String strValue) {
207
208        if (strValue == null) {
209            return;
210        }
211
212        name = StringUtil.capitalizeFirstLetter(name);
213        Method adderMethod =aggregationAssessor.findAdderMethod(name);
214
215        if (adderMethod == null) {
216            addError("No adder for property [" + name + "].");
217            return;
218        }
219
220        Class<?>[] paramTypes = adderMethod.getParameterTypes();
221        isSanityCheckSuccessful(name, adderMethod, paramTypes, strValue);
222
223        Object arg;
224        try {
225            arg = StringToObjectConverter.convertArg(this, strValue, paramTypes[0]);
226        } catch (Throwable t) {
227            addError("Conversion to type [" + paramTypes[0] + "] failed. ", t);
228            return;
229        }
230        if (arg != null) {
231            invokeMethodWithSingleParameterOnThisObject(adderMethod, strValue);
232        }
233    }
234
235    public void setComplexProperty(String name, Object complexProperty) {
236        Method setter = aggregationAssessor.findSetterMethod(name);
237
238        if (setter == null) {
239            addWarn("Not setter method for property [" + name + "] in " + obj.getClass().getName());
240
241            return;
242        }
243
244        Class<?>[] paramTypes = setter.getParameterTypes();
245
246        if (!isSanityCheckSuccessful(name, setter, paramTypes, complexProperty)) {
247            return;
248        }
249        try {
250            invokeMethodWithSingleParameterOnThisObject(setter, complexProperty);
251
252        } catch (Exception e) {
253            addError("Could not set component " + obj + " for parent component " + obj, e);
254        }
255    }
256
257    private boolean isSanityCheckSuccessful(String name, Method method, Class<?>[] params, Object complexProperty) {
258        Class<?> ccc = complexProperty.getClass();
259        if (params.length != 1) {
260            addError("Wrong number of parameters in setter method for property [" + name + "] in "
261                    + obj.getClass().getName());
262
263            return false;
264        }
265
266        if (!params[0].isAssignableFrom(complexProperty.getClass())) {
267            addError("A \"" + ccc.getName() + "\" object is not assignable to a \"" + params[0].getName()
268                    + "\" variable.");
269            addError("The class \"" + params[0].getName() + "\" was loaded by ");
270            addError("[" + params[0].getClassLoader() + "] whereas object of type ");
271            addError("\"" + ccc.getName() + "\" was loaded by [" + ccc.getClassLoader() + "].");
272            return false;
273        }
274
275        return true;
276    }
277
278    public Object getObj() {
279        return obj;
280    }
281
282
283    public Class<?> getClassNameViaImplicitRules(String name, AggregationType aggregationType,
284            DefaultNestedComponentRegistry registry) {
285        return aggregationAssessor.getClassNameViaImplicitRules(name, aggregationType, registry);
286    }
287
288
289
290
291
292    }