1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.beans;
19
20import java.lang.reflect.Constructor;
21import java.lang.reflect.Method;
22import java.lang.reflect.Modifier;
23import java.util.Vector;
24import org.apache.harmony.beans.internal.nls.Messages;
25
26public class PropertyDescriptor extends FeatureDescriptor {
27    private Method getter;
28
29    private Method setter;
30
31    private Class<?> propertyEditorClass;
32
33    private boolean constrained;
34
35    private boolean bound;
36
37    public PropertyDescriptor(String propertyName, Class<?> beanClass, String getterName,
38            String setterName) throws IntrospectionException {
39        super();
40        if (beanClass == null) {
41            throw new IntrospectionException(Messages.getString("beans.03")); //$NON-NLS-1$
42        }
43        if (propertyName == null || propertyName.length() == 0) {
44            throw new IntrospectionException(Messages.getString("beans.04")); //$NON-NLS-1$
45        }
46        this.setName(propertyName);
47        this.setDisplayName(propertyName);
48        if (setterName != null) {
49            if (hasMethod(beanClass, setterName)) {
50                setWriteMethod(beanClass, setterName);
51            } else {
52                throw new IntrospectionException(Messages.getString("beans.20")); //$NON-NLS-1$
53            }
54        }
55        if (getterName != null) {
56            if (hasMethod(beanClass, getterName)) {
57                setReadMethod(beanClass, getterName);
58            } else {
59                throw new IntrospectionException(Messages.getString("beans.1F")); //$NON-NLS-1$
60            }
61        }
62    }
63
64    public PropertyDescriptor(String propertyName, Method getter, Method setter)
65            throws IntrospectionException {
66        super();
67        if (propertyName == null || propertyName.length() == 0) {
68            throw new IntrospectionException(Messages.getString("beans.04")); //$NON-NLS-1$
69        }
70        this.setName(propertyName);
71        this.setDisplayName(propertyName);
72        setWriteMethod(setter);
73        setReadMethod(getter);
74    }
75
76    public PropertyDescriptor(String propertyName, Class<?> beanClass)
77            throws IntrospectionException {
78        String getterName;
79        String setterName;
80        if (beanClass == null) {
81            throw new IntrospectionException(Messages.getString("beans.03")); //$NON-NLS-1$
82        }
83        if (propertyName == null || propertyName.length() == 0) {
84            throw new IntrospectionException(Messages.getString("beans.04")); //$NON-NLS-1$
85        }
86        this.setName(propertyName);
87        this.setDisplayName(propertyName);
88        getterName = createDefaultMethodName(propertyName, "is"); //$NON-NLS-1$
89        if (hasMethod(beanClass, getterName)) {
90            setReadMethod(beanClass, getterName);
91        } else {
92            getterName = createDefaultMethodName(propertyName, "get"); //$NON-NLS-1$
93            if (hasMethod(beanClass, getterName)) {
94                setReadMethod(beanClass, getterName);
95            }
96        }
97        setterName = createDefaultMethodName(propertyName, "set"); //$NON-NLS-1$
98        if (hasMethod(beanClass, setterName)) {
99            setWriteMethod(beanClass, setterName);
100        }
101        if (getter == null && setter == null) {
102            throw new IntrospectionException(Messages.getString("beans.01", propertyName)); //$NON-NLS-1$
103        }
104    }
105
106    public void setWriteMethod(Method setter) throws IntrospectionException {
107        if (setter != null) {
108            int modifiers = setter.getModifiers();
109            if (!Modifier.isPublic(modifiers)) {
110                throw new IntrospectionException(Messages.getString("beans.05")); //$NON-NLS-1$
111            }
112            Class<?>[] parameterTypes = setter.getParameterTypes();
113            if (parameterTypes.length != 1) {
114                throw new IntrospectionException(Messages.getString("beans.06")); //$NON-NLS-1$
115            }
116            Class<?> parameterType = parameterTypes[0];
117            Class<?> propertyType = getPropertyType();
118            if (propertyType != null && !propertyType.equals(parameterType)) {
119                throw new IntrospectionException(Messages.getString("beans.07")); //$NON-NLS-1$
120            }
121        }
122        this.setter = setter;
123    }
124
125    public void setReadMethod(Method getter) throws IntrospectionException {
126        if (getter != null) {
127            int modifiers = getter.getModifiers();
128            if (!Modifier.isPublic(modifiers)) {
129                throw new IntrospectionException(Messages.getString("beans.0A")); //$NON-NLS-1$
130            }
131            Class<?>[] parameterTypes = getter.getParameterTypes();
132            if (parameterTypes.length != 0) {
133                throw new IntrospectionException(Messages.getString("beans.08")); //$NON-NLS-1$
134            }
135            Class<?> returnType = getter.getReturnType();
136            if (returnType.equals(Void.TYPE)) {
137                throw new IntrospectionException(Messages.getString("beans.33")); //$NON-NLS-1$
138            }
139            Class<?> propertyType = getPropertyType();
140            if ((propertyType != null) && !returnType.equals(propertyType)) {
141                throw new IntrospectionException(Messages.getString("beans.09")); //$NON-NLS-1$
142            }
143        }
144        this.getter = getter;
145    }
146
147    public Method getWriteMethod() {
148        return setter;
149    }
150
151    public Method getReadMethod() {
152        return getter;
153    }
154
155    @Override
156    public boolean equals(Object object) {
157        boolean result = (object != null && object instanceof PropertyDescriptor);
158        if (result) {
159            PropertyDescriptor pd = (PropertyDescriptor) object;
160            boolean gettersAreEqual = (this.getter == null) && (pd.getReadMethod() == null)
161                    || (this.getter != null) && (this.getter.equals(pd.getReadMethod()));
162            boolean settersAreEqual = (this.setter == null) && (pd.getWriteMethod() == null)
163                    || (this.setter != null) && (this.setter.equals(pd.getWriteMethod()));
164            boolean propertyTypesAreEqual = this.getPropertyType() == pd.getPropertyType();
165            boolean propertyEditorClassesAreEqual = this.getPropertyEditorClass() == pd
166                    .getPropertyEditorClass();
167            boolean boundPropertyAreEqual = this.isBound() == pd.isBound();
168            boolean constrainedPropertyAreEqual = this.isConstrained() == pd.isConstrained();
169            result = gettersAreEqual && settersAreEqual && propertyTypesAreEqual
170                    && propertyEditorClassesAreEqual && boundPropertyAreEqual
171                    && constrainedPropertyAreEqual;
172        }
173        return result;
174    }
175
176    public void setPropertyEditorClass(Class<?> propertyEditorClass) {
177        this.propertyEditorClass = propertyEditorClass;
178    }
179
180    public Class<?> getPropertyType() {
181        Class<?> result = null;
182        if (getter != null) {
183            result = getter.getReturnType();
184        } else if (setter != null) {
185            Class<?>[] parameterTypes = setter.getParameterTypes();
186            result = parameterTypes[0];
187        }
188        return result;
189    }
190
191    public Class<?> getPropertyEditorClass() {
192        return propertyEditorClass;
193    }
194
195    public void setConstrained(boolean constrained) {
196        this.constrained = constrained;
197    }
198
199    public void setBound(boolean bound) {
200        this.bound = bound;
201    }
202
203    public boolean isConstrained() {
204        return constrained;
205    }
206
207    public boolean isBound() {
208        return bound;
209    }
210
211    boolean hasMethod(Class<?> beanClass, String methodName) {
212        Method[] methods = findMethods(beanClass, methodName);
213        return (methods.length > 0);
214    }
215
216    String createDefaultMethodName(String propertyName, String prefix) {
217        String result = null;
218        if (propertyName != null) {
219            String bos = propertyName.substring(0, 1).toUpperCase();
220            String eos = propertyName.substring(1, propertyName.length());
221            result = prefix + bos + eos;
222        }
223        return result;
224    }
225
226    Method[] findMethods(Class<?> aClass, String methodName) {
227        Method[] allMethods = aClass.getMethods();
228        Vector<Method> matchedMethods = new Vector<Method>();
229        Method[] result;
230        for (Method method : allMethods) {
231            if (method.getName().equals(methodName)) {
232                matchedMethods.add(method);
233            }
234        }
235        result = new Method[matchedMethods.size()];
236        for (int j = 0; j < matchedMethods.size(); ++j) {
237            result[j] = matchedMethods.elementAt(j);
238        }
239        return result;
240    }
241
242    void setReadMethod(Class<?> beanClass, String getterName) {
243        boolean result = false;
244        Method[] getters = findMethods(beanClass, getterName);
245        for (Method element : getters) {
246            try {
247                setReadMethod(element);
248                result = true;
249            } catch (IntrospectionException ie) {
250            }
251            if (result) {
252                break;
253            }
254        }
255    }
256
257    void setWriteMethod(Class<?> beanClass, String setterName) throws IntrospectionException {
258        boolean result = false;
259        Method[] setters = findMethods(beanClass, setterName);
260        for (Method element : setters) {
261            try {
262                setWriteMethod(element);
263                result = true;
264            } catch (IntrospectionException ie) {
265            }
266            if (result) {
267                break;
268            }
269        }
270    }
271
272    public PropertyEditor createPropertyEditor(Object bean) {
273        PropertyEditor editor;
274        if (propertyEditorClass == null) {
275            return null;
276        }
277        if (!PropertyEditor.class.isAssignableFrom(propertyEditorClass)) {
278            // beans.48=Property editor is not assignable from the
279            // PropertyEditor interface
280            throw new ClassCastException(Messages.getString("beans.48")); //$NON-NLS-1$
281        }
282        try {
283            Constructor<?> constr;
284            try {
285                // try to look for the constructor with single Object argument
286                constr = propertyEditorClass.getConstructor(Object.class);
287                editor = (PropertyEditor) constr.newInstance(bean);
288            } catch (NoSuchMethodException e) {
289                // try no-argument constructor
290                constr = propertyEditorClass.getConstructor();
291                editor = (PropertyEditor) constr.newInstance();
292            }
293        } catch (Exception e) {
294            // beans.47=Unable to instantiate property editor
295            RuntimeException re = new RuntimeException(Messages.getString("beans.47"), e); //$NON-NLS-1$
296            throw re;
297        }
298        return editor;
299    }
300}
301