1 | /* |
2 | * Copyright 2007 brunella ltd |
3 | * |
4 | * Licensed under the LGPL Version 3 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * |
7 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
8 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
9 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
10 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
11 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
12 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
13 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
14 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
15 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
16 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
17 | * THE POSSIBILITY OF SUCH DAMAGE. |
18 | */ |
19 | package sf.qof.codegen; |
20 | |
21 | import java.lang.reflect.Constructor; |
22 | import java.lang.reflect.Method; |
23 | import java.util.ArrayList; |
24 | import java.util.Collections; |
25 | import java.util.Comparator; |
26 | import java.util.List; |
27 | |
28 | import sf.qof.Call; |
29 | import sf.qof.Delete; |
30 | import sf.qof.Insert; |
31 | import sf.qof.Query; |
32 | import sf.qof.Update; |
33 | import sf.qof.exception.ValidationException; |
34 | import sf.qof.mapping.Mapper; |
35 | import sf.qof.mapping.MappingFactory; |
36 | import sf.qof.mapping.MethodInfo; |
37 | import sf.qof.mapping.MethodInfoFactory; |
38 | import sf.qof.mapping.MethodParameterInfo; |
39 | import sf.qof.mapping.MethodReturnInfo; |
40 | import sf.qof.mapping.ParameterMapping; |
41 | import sf.qof.mapping.QueryType; |
42 | import sf.qof.mapping.ResultMapping; |
43 | import sf.qof.mapping.MappingFactory.MappingClassInfo; |
44 | import sf.qof.parser.ParameterDefinition; |
45 | import sf.qof.parser.ResultDefinition; |
46 | import sf.qof.parser.SqlParser; |
47 | import sf.qof.util.ReflectionUtils; |
48 | |
49 | /** |
50 | * Internal - AnnotationMapperFactory creates mappings from annotations and |
51 | * method definitions. |
52 | */ |
53 | public final class AnnotationMapperFactory { |
54 | |
55 | private AnnotationMapperFactory() { } |
56 | |
57 | public static Mapper create(Method method) { |
58 | MethodInfo methodInfo = MethodInfoFactory.createMethodInfo(method); |
59 | if (method.isAnnotationPresent(Query.class)) { |
60 | return create(methodInfo, method.getAnnotation(Query.class)); |
61 | } else if (method.isAnnotationPresent(Insert.class)) { |
62 | return create(methodInfo, method.getAnnotation(Insert.class)); |
63 | } else if (method.isAnnotationPresent(Update.class)) { |
64 | return create(methodInfo, method.getAnnotation(Update.class)); |
65 | } else if (method.isAnnotationPresent(Delete.class)) { |
66 | return create(methodInfo, method.getAnnotation(Delete.class)); |
67 | } else if (method.isAnnotationPresent(Call.class)) { |
68 | return create(methodInfo, method.getAnnotation(Call.class)); |
69 | } else { |
70 | return null; |
71 | } |
72 | } |
73 | |
74 | private static Mapper create(MethodInfo methodInfo, Query annotation) { |
75 | SqlParser parser = new SqlParser(annotation.sql(), false); |
76 | return new Mapper(methodInfo, QueryType.QUERY, parser.getSql(), |
77 | createParameterMappers(methodInfo, parser.getParameterDefinitions()), |
78 | createResultMappers(methodInfo, parser.getResultDefinitions())); |
79 | } |
80 | |
81 | private static Mapper create(MethodInfo methodInfo, Insert annotation) { |
82 | SqlParser parser = new SqlParser(annotation.sql(), false); |
83 | return new Mapper(methodInfo, QueryType.INSERT, parser.getSql(), |
84 | createParameterMappers(methodInfo, parser.getParameterDefinitions()), null); |
85 | } |
86 | |
87 | private static Mapper create(MethodInfo methodInfo, Update annotation) { |
88 | SqlParser parser = new SqlParser(annotation.sql(), false); |
89 | return new Mapper(methodInfo, QueryType.UPDATE, parser.getSql(), |
90 | createParameterMappers(methodInfo, parser.getParameterDefinitions()), null); |
91 | } |
92 | |
93 | private static Mapper create(MethodInfo methodInfo, Delete annotation) { |
94 | SqlParser parser = new SqlParser(annotation.sql(), false); |
95 | return new Mapper(methodInfo, QueryType.DELETE, parser.getSql(), |
96 | createParameterMappers(methodInfo, parser.getParameterDefinitions()), null); |
97 | } |
98 | |
99 | private static Mapper create(MethodInfo methodInfo, Call annotation) { |
100 | SqlParser parser = new SqlParser(annotation.sql(), true); |
101 | return new Mapper(methodInfo, QueryType.CALL, parser.getSql(), |
102 | createParameterMappers(methodInfo, parser.getParameterDefinitions()), |
103 | createResultMappers(methodInfo, parser.getResultDefinitions())); |
104 | } |
105 | |
106 | private static List<ParameterMapping> createParameterMappers(MethodInfo methodInfo, ParameterDefinition[] parameterDefs) { |
107 | List<ParameterMapping> list = new ArrayList<ParameterMapping>(); |
108 | for (ParameterDefinition parameter : parameterDefs) { |
109 | // get fields from annotation |
110 | String mappingType = parameter.getType(); |
111 | int index = parameter.getParameter() - 1; |
112 | if (index < 0 || index >= methodInfo.getParameterInfos().length) { |
113 | throw new ValidationException("Invalid parameter index for method " + methodInfo); |
114 | } |
115 | int[] sqlIndexes = parameter.getIndexes(); |
116 | String[] sqlColumns = parameter.getNames(); |
117 | if (sqlIndexes == null && sqlColumns == null) { |
118 | throw new ValidationException("Either indexes or columns must be defined"); |
119 | } |
120 | String field = parameter.getField(); |
121 | Class<?> type = null; |
122 | Class<?> collectionType = null; |
123 | Class<?> collectionElementType = null; |
124 | Class<?> arrayElementType = null; |
125 | Class<?> beanType = null; |
126 | Method getter = null; |
127 | |
128 | // get types |
129 | MethodParameterInfo parameterInfo = methodInfo.getParameterInfos()[index]; |
130 | collectionType = parameterInfo.getCollectionType(); |
131 | collectionElementType = parameterInfo.getCollectionElementType(); |
132 | arrayElementType = parameterInfo.getArrayElementType(); |
133 | |
134 | boolean usesArray = arrayElementType != null && arrayElementType != Byte.TYPE; |
135 | if (field == null) { |
136 | if (collectionType != null) { |
137 | type = collectionElementType; |
138 | } else if (usesArray) { |
139 | type = arrayElementType; |
140 | } else { |
141 | type = parameterInfo.getType(); |
142 | } |
143 | } else { |
144 | if (collectionType != null) { |
145 | beanType = collectionElementType; |
146 | } else { |
147 | beanType = parameterInfo.getType(); |
148 | } |
149 | getter = ReflectionUtils.findGetter(beanType, field); |
150 | if (getter == null) { |
151 | throw new ValidationException("Cannot find or access getter for " + field + " in class " + beanType.getName()); |
152 | } |
153 | type = getter.getReturnType(); |
154 | } |
155 | // create mapping |
156 | ParameterMapping mapping = MappingFactory.createParameterMapping(mappingType, index, type, collectionType, |
157 | beanType, getter, sqlIndexes, sqlColumns, usesArray); |
158 | list.add(mapping); |
159 | } |
160 | return list; |
161 | } |
162 | |
163 | private static List<ResultMapping> createResultMappers(MethodInfo methodInfo, ResultDefinition[] resultDefs) { |
164 | List<ResultMapping> list = new ArrayList<ResultMapping>(); |
165 | |
166 | MethodReturnInfo returnInfo = methodInfo.getReturnInfo(); |
167 | Constructor<?> constructor = findConstructor(returnInfo, resultDefs); |
168 | |
169 | for (ResultDefinition result : resultDefs) { |
170 | // get fields from annotation |
171 | String mappingType = result.getType(); |
172 | int[] sqlIndexes = result.getIndexes(); |
173 | String[] sqlColumns = result.getColumns(); |
174 | if (sqlIndexes == null && sqlColumns == null) { |
175 | throw new ValidationException("Either indexes or columns must be defined"); |
176 | } |
177 | Integer constructorParameter = null; |
178 | if (result.getConstructorParameter() > 0) { |
179 | constructorParameter = result.getConstructorParameter(); |
180 | } |
181 | String field = result.getField(); |
182 | Class<?> type = null; |
183 | Class<?> collectionType = returnInfo.getCollectionType(); |
184 | Class<?> collectionElementType = returnInfo.getCollectionElementType(); |
185 | Class<?> mapKeyType = null; |
186 | Class<?> beanType = null; |
187 | Method setter = null; |
188 | |
189 | // get types |
190 | |
191 | if (field == null) { |
192 | if (collectionType != null) { |
193 | type = collectionElementType; |
194 | if (result.isMapKey()) { |
195 | mapKeyType = returnInfo.getMapKeyType(); |
196 | } |
197 | } else { |
198 | type = returnInfo.getType(); |
199 | } |
200 | } else { |
201 | if (collectionType != null) { |
202 | beanType = returnInfo.getCollectionElementType(); |
203 | if (result.isMapKey()) { |
204 | mapKeyType = returnInfo.getMapKeyType(); |
205 | } |
206 | } else { |
207 | beanType = returnInfo.getType(); |
208 | } |
209 | MappingClassInfo info = MappingFactory.getResultMappingInfo(mappingType); |
210 | if (info == null) { |
211 | setter = ReflectionUtils.findSetter(beanType, field); |
212 | } else { |
213 | setter = ReflectionUtils.findSetter(beanType, field, info.getMappableTypes()); |
214 | } |
215 | if (setter == null) { |
216 | throw new ValidationException("Cannot find or access setter for " + field + " in class " + beanType.getName() |
217 | + " for mapping type " + mappingType); |
218 | } |
219 | type = setter.getParameterTypes()[0]; |
220 | } |
221 | |
222 | // create mapping |
223 | ResultMapping mapping = MappingFactory.createResultMapping(mappingType, type, collectionType, beanType, setter, |
224 | sqlIndexes, sqlColumns, mapKeyType, constructorParameter, (constructorParameter != null ? constructor : null)); |
225 | list.add(mapping); |
226 | } |
227 | return list; |
228 | } |
229 | |
230 | private static Constructor<?> findConstructor(MethodReturnInfo returnInfo, ResultDefinition[] resultDefs) { |
231 | List<ResultDefinition> constructorResultDefs = new ArrayList<ResultDefinition>(); |
232 | for (ResultDefinition resultDef : resultDefs) { |
233 | if (resultDef.getConstructorParameter() > 0) { |
234 | constructorResultDefs.add(resultDef); |
235 | } |
236 | } |
237 | if (constructorResultDefs.size() == 0) { |
238 | return null; |
239 | } |
240 | Collections.sort(constructorResultDefs, new Comparator<ResultDefinition>() { |
241 | public int compare(ResultDefinition o1, ResultDefinition o2) { |
242 | return o1.getConstructorParameter() - o2.getConstructorParameter(); |
243 | } |
244 | }); |
245 | |
246 | Class<?> type; |
247 | if (returnInfo.getCollectionType() != null) { |
248 | type = returnInfo.getCollectionElementType(); |
249 | } else { |
250 | type = returnInfo.getType(); |
251 | } |
252 | |
253 | Constructor<?>[] constructors = type.getConstructors(); |
254 | |
255 | Constructor<?> matchingConstructor = null; |
256 | for (Constructor<?> constructor : constructors) { |
257 | Class<?>[] parameterTypes = constructor.getParameterTypes(); |
258 | if (parameterTypes.length != constructorResultDefs.size()) { |
259 | continue; |
260 | } |
261 | for (int i = 0; i < parameterTypes.length; i++) { |
262 | ResultDefinition def = constructorResultDefs.get(i); |
263 | MappingClassInfo info = MappingFactory.getResultMappingInfo(def.getType()); |
264 | if (!info.getMappableTypes().contains(parameterTypes[i])) { |
265 | break; |
266 | } |
267 | if (i + 1 == parameterTypes.length) { |
268 | matchingConstructor = constructor; |
269 | break; |
270 | } |
271 | } |
272 | if (matchingConstructor != null) { |
273 | break; |
274 | } |
275 | } |
276 | |
277 | if (matchingConstructor == null) { |
278 | // try again but relaxed rules |
279 | for (Constructor<?> constructor : constructors) { |
280 | Class<?>[] parameterTypes = constructor.getParameterTypes(); |
281 | if (parameterTypes.length != constructorResultDefs.size()) { |
282 | continue; |
283 | } |
284 | for (int i = 0; i < parameterTypes.length; i++) { |
285 | ResultDefinition def = constructorResultDefs.get(i); |
286 | MappingClassInfo info = MappingFactory.getResultMappingInfo(def.getType()); |
287 | boolean found = false; |
288 | for (Class<?> mappableType : info.getMappableTypes()) { |
289 | if (mappableType.isAssignableFrom(parameterTypes[i])) { |
290 | found = true; |
291 | break; |
292 | } |
293 | } |
294 | if (!found) { |
295 | break; |
296 | } |
297 | if (i + 1 == parameterTypes.length) { |
298 | matchingConstructor = constructor; |
299 | break; |
300 | } |
301 | } |
302 | if (matchingConstructor != null) { |
303 | break; |
304 | } |
305 | } |
306 | } |
307 | |
308 | if (matchingConstructor == null) { |
309 | throw new RuntimeException("Could not find matching constructor in " + type); |
310 | } |
311 | |
312 | return matchingConstructor; |
313 | } |
314 | } |