1/*
2 * Copyright (c) 2007 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5package org.mockito.internal.util.reflection;
6
7import java.lang.reflect.Field;
8import java.lang.reflect.InvocationTargetException;
9import java.lang.reflect.Method;
10import java.util.Locale;
11
12/**
13 * This utility class will call the setter of the property to inject a new value.
14 */
15public class BeanPropertySetter {
16
17    private static final String SET_PREFIX = "set";
18
19    private final Object target;
20    private boolean reportNoSetterFound;
21    private final Field field;
22
23    /**
24     * New BeanPropertySetter
25     * @param target The target on which the setter must be invoked
26     * @param propertyField The field that should be accessed with the setter
27     * @param reportNoSetterFound Allow the set method to raise an Exception if the setter cannot be found
28     */
29    public BeanPropertySetter(final Object target, final Field propertyField, boolean reportNoSetterFound) {
30        this.field = propertyField;
31        this.target = target;
32        this.reportNoSetterFound = reportNoSetterFound;
33    }
34
35    /**
36     * New BeanPropertySetter that don't report failure
37     * @param target The target on which the setter must be invoked
38     * @param propertyField The propertyField that must be accessed through a setter
39     */
40    public BeanPropertySetter(final Object target, final Field propertyField) {
41        this(target, propertyField, false);
42    }
43
44    /**
45     * Set the value to the property represented by this {@link BeanPropertySetter}
46     * @param value the new value to pass to the property setter
47     * @return <code>true</code> if the value has been injected, <code>false</code> otherwise
48     * @throws RuntimeException Can be thrown if the setter threw an exception, if the setter is not accessible
49     *          or, if <code>reportNoSetterFound</code> and setter could not be found.
50     */
51    public boolean set(final Object value) {
52
53        AccessibilityChanger changer = new AccessibilityChanger();
54        Method writeMethod = null;
55        try {
56            writeMethod = target.getClass().getMethod(setterName(field.getName()), field.getType());
57
58            changer.enableAccess(writeMethod);
59            writeMethod.invoke(target, value);
60            return true;
61        } catch (InvocationTargetException e) {
62            throw new RuntimeException("Setter '" + writeMethod + "' of '" + target + "' with value '" + value + "' threw exception : '" + e.getTargetException() + "'", e);
63        } catch (IllegalAccessException e) {
64            throw new RuntimeException("Access not authorized on field '" + field + "' of object '" + target + "' with value: '" + value + "'", e);
65        } catch (NoSuchMethodException e) {
66            reportNoSetterFound();
67        } finally {
68            if(writeMethod != null) {
69                changer.safelyDisableAccess(writeMethod);
70            }
71        }
72
73        reportNoSetterFound();
74        return false;
75    }
76
77    /**
78     * Retrieve the setter name from the field name.
79     *
80     * <p>Implementation is based on the code of {@link java.beans.Introspector}.</p>
81     *
82     * @param fieldName the Field name
83     * @return Setter name.
84     */
85    private String setterName(String fieldName) {
86        return new StringBuilder(SET_PREFIX)
87                .append(fieldName.substring(0, 1).toUpperCase(Locale.ENGLISH))
88                .append(fieldName.substring(1))
89                .toString();
90    }
91
92    private void reportNoSetterFound() {
93        if(reportNoSetterFound) {
94            throw new RuntimeException("Problems setting value on object: [" + target + "] for property : [" + field.getName() + "], setter not found");
95        }
96    }
97
98}
99