View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.beans;
18  
19  import java.beans.IntrospectionException;
20  import java.beans.PropertyDescriptor;
21  import java.lang.reflect.Method;
22  import java.util.HashSet;
23  import java.util.Set;
24  
25  import org.apache.commons.logging.LogFactory;
26  
27  import org.springframework.core.BridgeMethodResolver;
28  import org.springframework.core.GenericTypeResolver;
29  import org.springframework.core.MethodParameter;
30  import org.springframework.util.ClassUtils;
31  import org.springframework.util.ObjectUtils;
32  import org.springframework.util.StringUtils;
33  
34  /**
35   * Extension of the standard JavaBeans {@link PropertyDescriptor} class,
36   * overriding {@code getPropertyType()} such that a generically declared
37   * type variable will be resolved against the containing bean class.
38   *
39   * @author Juergen Hoeller
40   * @since 2.5.2
41   */
42  final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
43  
44  	private final Class<?> beanClass;
45  
46  	private final Method readMethod;
47  
48  	private final Method writeMethod;
49  
50  	private volatile Set<Method> ambiguousWriteMethods;
51  
52  	private MethodParameter writeMethodParameter;
53  
54  	private Class<?> propertyType;
55  
56  	private final Class<?> propertyEditorClass;
57  
58  
59  	public GenericTypeAwarePropertyDescriptor(Class<?> beanClass, String propertyName,
60  			Method readMethod, Method writeMethod, Class<?> propertyEditorClass)
61  			throws IntrospectionException {
62  
63  		super(propertyName, null, null);
64  
65  		if (beanClass == null)  {
66  			throw new IntrospectionException("Bean class must not be null");
67  		}
68  		this.beanClass = beanClass;
69  
70  		Method readMethodToUse = BridgeMethodResolver.findBridgedMethod(readMethod);
71  		Method writeMethodToUse = BridgeMethodResolver.findBridgedMethod(writeMethod);
72  		if (writeMethodToUse == null && readMethodToUse != null) {
73  			// Fallback: Original JavaBeans introspection might not have found matching setter
74  			// method due to lack of bridge method resolution, in case of the getter using a
75  			// covariant return type whereas the setter is defined for the concrete property type.
76  			Method candidate = ClassUtils.getMethodIfAvailable(
77  					this.beanClass, "set" + StringUtils.capitalize(getName()), (Class<?>[]) null);
78  			if (candidate != null && candidate.getParameterTypes().length == 1) {
79  				writeMethodToUse = candidate;
80  			}
81  		}
82  		this.readMethod = readMethodToUse;
83  		this.writeMethod = writeMethodToUse;
84  
85  		if (this.writeMethod != null) {
86  			if (this.readMethod == null) {
87  				// Write method not matched against read method: potentially ambiguous through
88  				// several overloaded variants, in which case an arbitrary winner has been chosen
89  				// by the JDK's JavaBeans Introspector...
90  				Set<Method> ambiguousCandidates = new HashSet<Method>();
91  				for (Method method : beanClass.getMethods()) {
92  					if (method.getName().equals(writeMethodToUse.getName()) &&
93  							!method.equals(writeMethodToUse) && !method.isBridge()) {
94  						ambiguousCandidates.add(method);
95  					}
96  				}
97  				if (!ambiguousCandidates.isEmpty()) {
98  					this.ambiguousWriteMethods = ambiguousCandidates;
99  				}
100 			}
101 			this.writeMethodParameter = new MethodParameter(this.writeMethod, 0);
102 			GenericTypeResolver.resolveParameterType(this.writeMethodParameter, this.beanClass);
103 		}
104 
105 		if (this.readMethod != null) {
106 			this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass);
107 		}
108 		else if (this.writeMethodParameter != null) {
109 			this.propertyType = this.writeMethodParameter.getParameterType();
110 		}
111 
112 		this.propertyEditorClass = propertyEditorClass;
113 	}
114 
115 
116 	public Class<?> getBeanClass() {
117 		return this.beanClass;
118 	}
119 
120 	@Override
121 	public Method getReadMethod() {
122 		return this.readMethod;
123 	}
124 
125 	@Override
126 	public Method getWriteMethod() {
127 		return this.writeMethod;
128 	}
129 
130 	public Method getWriteMethodForActualAccess() {
131 		Set<Method> ambiguousCandidates = this.ambiguousWriteMethods;
132 		if (ambiguousCandidates != null) {
133 			this.ambiguousWriteMethods = null;
134 			LogFactory.getLog(GenericTypeAwarePropertyDescriptor.class).warn("Invalid JavaBean property '" +
135 					getName() + "' being accessed! Ambiguous write methods found next to actually used [" +
136 					this.writeMethod + "]: " + ambiguousCandidates);
137 		}
138 		return this.writeMethod;
139 	}
140 
141 	public MethodParameter getWriteMethodParameter() {
142 		return this.writeMethodParameter;
143 	}
144 
145 	@Override
146 	public Class<?> getPropertyType() {
147 		return this.propertyType;
148 	}
149 
150 	@Override
151 	public Class<?> getPropertyEditorClass() {
152 		return this.propertyEditorClass;
153 	}
154 
155 
156 	@Override
157 	public boolean equals(Object other) {
158 		if (this == other) {
159 			return true;
160 		}
161 		if (!(other instanceof GenericTypeAwarePropertyDescriptor)) {
162 			return false;
163 		}
164 		GenericTypeAwarePropertyDescriptor otherPd = (GenericTypeAwarePropertyDescriptor) other;
165 		return (getBeanClass().equals(otherPd.getBeanClass()) && PropertyDescriptorUtils.equals(this, otherPd));
166 	}
167 
168 	@Override
169 	public int hashCode() {
170 		int hashCode = getBeanClass().hashCode();
171 		hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(getReadMethod());
172 		hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(getWriteMethod());
173 		return hashCode;
174 	}
175 
176 }