| 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 | } |