1/*
2 * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package com.sun.beans.decoder;
26
27import com.sun.beans.finder.MethodFinder;
28
29import java.beans.IndexedPropertyDescriptor;
30import java.beans.IntrospectionException;
31import java.beans.Introspector;
32import java.beans.PropertyDescriptor;
33
34import java.lang.reflect.Array;
35import java.lang.reflect.InvocationTargetException;
36import java.lang.reflect.Method;
37
38import sun.reflect.misc.MethodUtil;
39
40/**
41 * This class is intended to handle <property> element.
42 * This element simplifies access to the properties.
43 * If the {@code index} attribute is specified
44 * this element uses additional {@code int} parameter.
45 * If the {@code name} attribute is not specified
46 * this element uses method "get" as getter
47 * and method "set" as setter.
48 * This element defines getter if it contains no argument.
49 * It returns the value of the property in this case.
50 * For example:<pre>
51 * &lt;property name="object" index="10"/&gt;</pre>
52 * is shortcut to<pre>
53 * &lt;method name="getObject"&gt;
54 *     &lt;int&gt;10&lt;/int&gt;
55 * &lt;/method&gt;</pre>
56 * which is equivalent to {@code getObject(10)} in Java code.
57 * This element defines setter if it contains one argument.
58 * It does not return the value of the property in this case.
59 * For example:<pre>
60 * &lt;property&gt;&lt;int&gt;0&lt;/int&gt;&lt;/property&gt;</pre>
61 * is shortcut to<pre>
62 * &lt;method name="set"&gt;
63 *     &lt;int&gt;0&lt;/int&gt;
64 * &lt;/method&gt;</pre>
65 * which is equivalent to {@code set(0)} in Java code.
66 * <p>The following attributes are supported:
67 * <dl>
68 * <dt>name
69 * <dd>the property name
70 * <dt>index
71 * <dd>the property index
72 * <dt>id
73 * <dd>the identifier of the variable that is intended to store the result
74 * </dl>
75 *
76 * @since 1.7
77 *
78 * @author Sergey A. Malenkov
79 */
80final class PropertyElementHandler extends AccessorElementHandler {
81    static final String GETTER = "get"; // NON-NLS: the getter prefix
82    static final String SETTER = "set"; // NON-NLS: the setter prefix
83
84    private Integer index;
85
86    /**
87     * Parses attributes of the element.
88     * The following attributes are supported:
89     * <dl>
90     * <dt>name
91     * <dd>the property name
92     * <dt>index
93     * <dd>the property index
94     * <dt>id
95     * <dd>the identifier of the variable that is intended to store the result
96     * </dl>
97     *
98     * @param name   the attribute name
99     * @param value  the attribute value
100     */
101    @Override
102    public void addAttribute(String name, String value) {
103        if (name.equals("index")) { // NON-NLS: the attribute name
104            this.index = Integer.valueOf(value);
105        } else {
106            super.addAttribute(name, value);
107        }
108    }
109
110    /**
111     * Tests whether the value of this element can be used
112     * as an argument of the element that contained in this one.
113     *
114     * @return {@code true} if the value of this element should be used
115     *         as an argument of the element that contained in this one,
116     *         {@code false} otherwise
117     */
118    @Override
119    protected boolean isArgument() {
120        return false; // non-static accessor cannot be used an argument
121    }
122
123    /**
124     * Returns the value of the property with specified {@code name}.
125     *
126     * @param name  the name of the property
127     * @return the value of the specified property
128     */
129    @Override
130    protected Object getValue(String name) {
131        try {
132            return getPropertyValue(getContextBean(), name, this.index);
133        }
134        catch (Exception exception) {
135            getOwner().handleException(exception);
136        }
137        return null;
138    }
139
140    /**
141     * Sets the new value for the property with specified {@code name}.
142     *
143     * @param name   the name of the property
144     * @param value  the new value for the specified property
145     */
146    @Override
147    protected void setValue(String name, Object value) {
148        try {
149            setPropertyValue(getContextBean(), name, this.index, value);
150        }
151        catch (Exception exception) {
152            getOwner().handleException(exception);
153        }
154    }
155
156    /**
157     * Performs the search of the getter for the property
158     * with specified {@code name} in specified class
159     * and returns value of the property.
160     *
161     * @param bean   the context bean that contains property
162     * @param name   the name of the property
163     * @param index  the index of the indexed property
164     * @return the value of the property
165     * @throws IllegalAccessException    if the property is not accesible
166     * @throws IntrospectionException    if the bean introspection is failed
167     * @throws InvocationTargetException if the getter cannot be invoked
168     * @throws NoSuchMethodException     if the getter is not found
169     */
170    private static Object getPropertyValue(Object bean, String name, Integer index) throws IllegalAccessException, IntrospectionException, InvocationTargetException, NoSuchMethodException {
171        Class<?> type = bean.getClass();
172        if (index == null) {
173            return MethodUtil.invoke(findGetter(type, name), bean, new Object[] {});
174        } else if (type.isArray() && (name == null)) {
175            return Array.get(bean, index);
176        } else {
177            return MethodUtil.invoke(findGetter(type, name, int.class), bean, new Object[] {index});
178        }
179    }
180
181    /**
182     * Performs the search of the setter for the property
183     * with specified {@code name} in specified class
184     * and updates value of the property.
185     *
186     * @param bean   the context bean that contains property
187     * @param name   the name of the property
188     * @param index  the index of the indexed property
189     * @param value  the new value for the property
190     * @throws IllegalAccessException    if the property is not accesible
191     * @throws IntrospectionException    if the bean introspection is failed
192     * @throws InvocationTargetException if the setter cannot be invoked
193     * @throws NoSuchMethodException     if the setter is not found
194     */
195    private static void setPropertyValue(Object bean, String name, Integer index, Object value) throws IllegalAccessException, IntrospectionException, InvocationTargetException, NoSuchMethodException {
196        Class<?> type = bean.getClass();
197        Class<?> param = (value != null)
198                ? value.getClass()
199                : null;
200
201        if (index == null) {
202            MethodUtil.invoke(findSetter(type, name, param), bean, new Object[] {value});
203        } else if (type.isArray() && (name == null)) {
204            Array.set(bean, index, value);
205        } else {
206            MethodUtil.invoke(findSetter(type, name, int.class, param), bean, new Object[] {index, value});
207        }
208    }
209
210    /**
211     * Performs the search of the getter for the property
212     * with specified {@code name} in specified class.
213     *
214     * @param type  the class that contains method
215     * @param name  the name of the property
216     * @param args  the method arguments
217     * @return method object that represents found getter
218     * @throws IntrospectionException if the bean introspection is failed
219     * @throws NoSuchMethodException  if method is not found
220     */
221    private static Method findGetter(Class<?> type, String name, Class<?>...args) throws IntrospectionException, NoSuchMethodException {
222        if (name == null) {
223            return MethodFinder.findInstanceMethod(type, GETTER, args);
224        }
225        PropertyDescriptor pd = getProperty(type, name);
226        if (args.length == 0) {
227            Method method = pd.getReadMethod();
228            if (method != null) {
229                return method;
230            }
231        } else if (pd instanceof IndexedPropertyDescriptor) {
232            IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
233            Method method = ipd.getIndexedReadMethod();
234            if (method != null) {
235                return method;
236            }
237        }
238        throw new IntrospectionException("Could not find getter for the " + name + " property");
239    }
240
241    /**
242     * Performs the search of the setter for the property
243     * with specified {@code name} in specified class.
244     *
245     * @param type  the class that contains method
246     * @param name  the name of the property
247     * @param args  the method arguments
248     * @return method object that represents found setter
249     * @throws IntrospectionException if the bean introspection is failed
250     * @throws NoSuchMethodException  if method is not found
251     */
252    private static Method findSetter(Class<?> type, String name, Class<?>...args) throws IntrospectionException, NoSuchMethodException {
253        if (name == null) {
254            return MethodFinder.findInstanceMethod(type, SETTER, args);
255        }
256        PropertyDescriptor pd = getProperty(type, name);
257        if (args.length == 1) {
258            Method method = pd.getWriteMethod();
259            if (method != null) {
260                return method;
261            }
262        } else if (pd instanceof IndexedPropertyDescriptor) {
263            IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
264            Method method = ipd.getIndexedWriteMethod();
265            if (method != null) {
266                return method;
267            }
268        }
269        throw new IntrospectionException("Could not find setter for the " + name + " property");
270    }
271
272    /**
273     * Performs the search of the descriptor for the property
274     * with specified {@code name} in specified class.
275     *
276     * @param type  the class to introspect
277     * @param name  the property name
278     * @return descriptor for the named property
279     * @throws IntrospectionException if property descriptor is not found
280     */
281    private static PropertyDescriptor getProperty(Class<?> type, String name) throws IntrospectionException {
282        for (PropertyDescriptor pd : Introspector.getBeanInfo(type).getPropertyDescriptors()) {
283            if (name.equals(pd.getName())) {
284                return pd;
285            }
286        }
287        throw new IntrospectionException("Could not find the " + name + " property descriptor");
288    }
289}
290