1
+ /*
2
+ * Copyright 2002-2011 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
+ package org .springframework .core .convert ;
17
+
18
+ import java .lang .annotation .Annotation ;
19
+ import java .lang .reflect .Field ;
20
+ import java .lang .reflect .Method ;
21
+ import java .util .LinkedHashMap ;
22
+ import java .util .Map ;
23
+
24
+ import org .springframework .core .GenericTypeResolver ;
25
+ import org .springframework .core .MethodParameter ;
26
+ import org .springframework .util .ReflectionUtils ;
27
+ import org .springframework .util .StringUtils ;
28
+
29
+ /**
30
+ * A description of a JavaBeans Property that allows us to avoid a dependency on java.beans.PropertyDescriptor.
31
+ * java.beans is not available in a number of environments (e.g. Android, Java ME), so this is desirable.
32
+ * Used to build a TypeDescriptor from a property location.
33
+ * The built TypeDescriptor can then be used to convert from/to the property type.
34
+ * @author Keith Donald
35
+ * @see TypeDescriptor#TypeDescriptor(Property)
36
+ * @see TypeDescriptor#nested(Property, int)
37
+ */
38
+ public final class Property {
39
+
40
+ private final Class <?> objectType ;
41
+
42
+ private final Method readMethod ;
43
+
44
+ private final Method writeMethod ;
45
+
46
+ private final String name ;
47
+
48
+ private final MethodParameter methodParameter ;
49
+
50
+ private final Annotation [] annotations ;
51
+
52
+ public Property (Class <?> objectType , Method readMethod , Method writeMethod ) {
53
+ this .objectType = objectType ;
54
+ this .readMethod = readMethod ;
55
+ this .writeMethod = writeMethod ;
56
+ this .methodParameter = resolveMethodParameter ();
57
+ this .name = resolveName ();
58
+ this .annotations = resolveAnnotations ();
59
+ }
60
+
61
+ /**
62
+ * The object declaring this property, either directly or in a superclass the object extends.
63
+ */
64
+ public Class <?> getObjectType () {
65
+ return objectType ;
66
+ }
67
+
68
+ /**
69
+ * The name of the property e.g. 'foo'.
70
+ */
71
+ public String getName () {
72
+ return name ;
73
+ }
74
+
75
+ /**
76
+ * The property type e.g. java.lang.String.
77
+ */
78
+ public Class <?> getType () {
79
+ return methodParameter .getParameterType ();
80
+ }
81
+
82
+ /**
83
+ * The property getter method e.g. getFoo()
84
+ */
85
+ public Method getReadMethod () {
86
+ return readMethod ;
87
+ }
88
+
89
+ /**
90
+ * The property setter method e.g. setFoo(String).
91
+ */
92
+ public Method getWriteMethod () {
93
+ return writeMethod ;
94
+ }
95
+
96
+ // package private
97
+
98
+ MethodParameter getMethodParameter () {
99
+ return methodParameter ;
100
+ }
101
+
102
+ Annotation [] getAnnotations () {
103
+ return annotations ;
104
+ }
105
+
106
+ // internal helpers
107
+
108
+ private String resolveName () {
109
+ if (readMethod != null ) {
110
+ int index = readMethod .getName ().indexOf ("get" );
111
+ if (index != -1 ) {
112
+ index += 3 ;
113
+ } else {
114
+ index = readMethod .getName ().indexOf ("is" );
115
+ if (index == -1 ) {
116
+ throw new IllegalArgumentException ("Not a getter method" );
117
+ }
118
+ index += 2 ;
119
+ }
120
+ return StringUtils .uncapitalize (readMethod .getName ().substring (index ));
121
+ } else {
122
+ int index = writeMethod .getName ().indexOf ("set" ) + 3 ;
123
+ if (index == -1 ) {
124
+ throw new IllegalArgumentException ("Not a setter method" );
125
+ }
126
+ return StringUtils .uncapitalize (writeMethod .getName ().substring (index ));
127
+ }
128
+ }
129
+
130
+ private MethodParameter resolveMethodParameter () {
131
+ MethodParameter read = resolveReadMethodParameter ();
132
+ MethodParameter write = resolveWriteMethodParameter ();
133
+ if (read == null && write == null ) {
134
+ throw new IllegalStateException ("Property is neither readable or writeable" );
135
+ }
136
+ if (read != null && write != null && !read .getParameterType ().equals (write .getParameterType ())) {
137
+ throw new IllegalStateException ("Read and write parameter types are not the same" );
138
+ }
139
+ return read != null ? read : write ;
140
+ }
141
+
142
+ private MethodParameter resolveReadMethodParameter () {
143
+ if (getReadMethod () == null ) {
144
+ return null ;
145
+ }
146
+ return resolveParameterType (new MethodParameter (getReadMethod (), -1 ));
147
+ }
148
+
149
+ private MethodParameter resolveWriteMethodParameter () {
150
+ if (getWriteMethod () == null ) {
151
+ return null ;
152
+ }
153
+ return resolveParameterType (new MethodParameter (getWriteMethod (), 0 ));
154
+ }
155
+
156
+ private MethodParameter resolveParameterType (MethodParameter parameter ) {
157
+ // needed to resolve generic property types that parameterized by sub-classes e.g. T getFoo();
158
+ GenericTypeResolver .resolveParameterType (parameter , getObjectType ());
159
+ return parameter ;
160
+ }
161
+
162
+ private Annotation [] resolveAnnotations () {
163
+ Map <Class <?>, Annotation > annMap = new LinkedHashMap <Class <?>, Annotation >();
164
+ Method readMethod = getReadMethod ();
165
+ if (readMethod != null ) {
166
+ for (Annotation ann : readMethod .getAnnotations ()) {
167
+ annMap .put (ann .annotationType (), ann );
168
+ }
169
+ }
170
+ Method writeMethod = getWriteMethod ();
171
+ if (writeMethod != null ) {
172
+ for (Annotation ann : writeMethod .getAnnotations ()) {
173
+ annMap .put (ann .annotationType (), ann );
174
+ }
175
+ }
176
+ Field field = getField ();
177
+ if (field != null ) {
178
+ for (Annotation ann : field .getAnnotations ()) {
179
+ annMap .put (ann .annotationType (), ann );
180
+ }
181
+ }
182
+ return annMap .values ().toArray (new Annotation [annMap .size ()]);
183
+ }
184
+
185
+ private Field getField () {
186
+ String name = getName ();
187
+ if (!StringUtils .hasLength (name )) {
188
+ return null ;
189
+ }
190
+ Class <?> declaringClass = declaringClass ();
191
+ Field field = ReflectionUtils .findField (declaringClass , name );
192
+ if (field == null ) {
193
+ // Same lenient fallback checking as in CachedIntrospectionResults...
194
+ field = ReflectionUtils .findField (declaringClass ,
195
+ name .substring (0 , 1 ).toLowerCase () + name .substring (1 ));
196
+ if (field == null ) {
197
+ field = ReflectionUtils .findField (declaringClass ,
198
+ name .substring (0 , 1 ).toUpperCase () + name .substring (1 ));
199
+ }
200
+ }
201
+ return field ;
202
+ }
203
+
204
+ private Class <?> declaringClass () {
205
+ if (getReadMethod () != null ) {
206
+ return getReadMethod ().getDeclaringClass ();
207
+ } else {
208
+ return getWriteMethod ().getDeclaringClass ();
209
+ }
210
+ }
211
+
212
+ }
0 commit comments