diff --git a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeResultSetMapper.java b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeResultSetMapper.java index 32b103c..bd9e0ed 100644 --- a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeResultSetMapper.java +++ b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeResultSetMapper.java @@ -1,10 +1,10 @@ package com.moparisthebest.jdbc.codegen; -import com.moparisthebest.jdbc.CompilingRowToObjectMapper; import com.moparisthebest.jdbc.ResultSetMapper; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; @@ -13,9 +13,9 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import java.io.IOException; import java.io.Writer; -import java.lang.reflect.Type; import java.util.*; +import static com.moparisthebest.jdbc.codegen.JdbcMapperProcessor.typeMirrorStringNoGenerics; import static com.moparisthebest.jdbc.codegen.JdbcMapperProcessor.typeMirrorToClass; /** @@ -38,6 +38,20 @@ public class CompileTimeResultSetMapper { listIteratorType = types.getDeclaredType(elements.getTypeElement(ListIterator.class.getCanonicalName()), types.getWildcardType(null, null)); } + @SuppressWarnings({"unchecked"}) + public static String getConcreteClassCanonicalName(final TypeMirror returnType, final Class defaultConcreteClass) { + final Set modifiers = ((DeclaredType) returnType).asElement().getModifiers(); + if (modifiers.contains(Modifier.ABSTRACT)) { // todo: no interface? + try { + final Class concrete = (Class) ResultSetMapper.interfaceToConcrete.get(typeMirrorToClass(returnType)); + return (concrete == null ? (Class) defaultConcreteClass : concrete).getCanonicalName(); + } catch (ClassNotFoundException e) { + // ignore? + } + } + return typeMirrorStringNoGenerics(returnType); + } + @SuppressWarnings({"unchecked"}) public void mapToResultType(final Writer w, final String[] keys, final ExecutableElement eeMethod, final int arrayMaxLength, final Calendar cal) throws IOException, NoSuchMethodException, ClassNotFoundException { //final Method m = fromExecutableElement(eeMethod); @@ -46,176 +60,141 @@ public class CompileTimeResultSetMapper { //final Class returnType = typeMirrorToClass(returnTypeMirror); if (returnTypeMirror.getKind() == TypeKind.ARRAY) { final TypeMirror componentType = ((ArrayType) returnTypeMirror).getComponentType(); - toArray(w, keys, componentType, typeMirrorToClass(componentType), arrayMaxLength, cal); + toArray(w, keys, componentType, arrayMaxLength, cal); } else if (types.isAssignable(returnTypeMirror, collectionType)) { final List typeArguments = ((DeclaredType) returnTypeMirror).getTypeArguments(); - toCollection(w, keys, returnTypeMirror, (Class)typeMirrorToClass(returnTypeMirror), typeArguments.get(0), (Class) getActualTypeArguments(typeArguments)[0], arrayMaxLength, cal); + toCollection(w, keys, returnTypeMirror, typeArguments.get(0), arrayMaxLength, cal); } else if (types.isAssignable(returnTypeMirror, mapType)) { final List typeArguments = ((DeclaredType) returnTypeMirror).getTypeArguments(); - final Type[] typeArgs = getActualTypeArguments(typeArguments); //if (types[1] instanceof ParameterizedType) { // for collectionMaps if (types.isAssignable(returnTypeMirror, mapCollectionType)) { // for collectionMaps - //final ParameterizedType pt = (ParameterizedType) types[1]; - //final Class collectionType = (Class) pt.getRawType(); final TypeMirror collectionTypeMirror = typeArguments.get(1); - final Class collectionType = typeMirrorToClass(collectionTypeMirror); - if (Collection.class.isAssignableFrom(collectionType)) { - final TypeMirror componentTypeMirror = ((DeclaredType) collectionTypeMirror).getTypeArguments().get(0); - //final Class componentType = (Class) pt.getActualTypeArguments()[0]; - final Class componentType = typeMirrorToClass(componentTypeMirror); - toMapCollection(w, keys, - returnTypeMirror, (Class)typeMirrorToClass(returnTypeMirror), - typeArguments.get(0), (Class) typeArgs[0], - collectionTypeMirror, collectionType, - componentTypeMirror, componentType, - arrayMaxLength, cal); - return; - } + final TypeMirror componentTypeMirror = ((DeclaredType) collectionTypeMirror).getTypeArguments().get(0); + toMapCollection(w, keys, + returnTypeMirror, + typeArguments.get(0), + collectionTypeMirror, + componentTypeMirror, + arrayMaxLength, cal); + return; } - toMap(w, keys, returnTypeMirror, (Class)typeMirrorToClass(returnTypeMirror), typeArguments.get(0), (Class) typeArgs[0], typeArguments.get(1), (Class) typeArgs[1], arrayMaxLength, cal); + toMap(w, keys, returnTypeMirror, typeArguments.get(0), typeArguments.get(1), arrayMaxLength, cal); } else if (types.isAssignable(returnTypeMirror, iteratorType)) { final List typeArguments = ((DeclaredType) returnTypeMirror).getTypeArguments(); if (types.isAssignable(returnTypeMirror, listIteratorType)) - toListIterator(w, keys, typeArguments.get(0), (Class) getActualTypeArguments(typeArguments)[0], arrayMaxLength, cal); + toListIterator(w, keys, typeArguments.get(0), arrayMaxLength, cal); else - toIterator(w, keys, typeArguments.get(0), (Class) getActualTypeArguments(typeArguments)[0], arrayMaxLength, cal); + toIterator(w, keys, typeArguments.get(0), arrayMaxLength, cal); } else { - toObject(w, keys, returnTypeMirror, typeMirrorToClass(returnTypeMirror), cal); + toObject(w, keys, returnTypeMirror, cal); } } - public CompilingRowToObjectMapper getRowMapper(final String[] keys, Class returnTypeClass, Calendar cal, Class mapValType, Class mapKeyType) { - return new CompilingRowToObjectMapper(keys, returnTypeClass, cal, mapValType, mapKeyType); + @SuppressWarnings("unchecked") + public CompileTimeRowToObjectMapper getRowMapper(final String[] keys, TypeMirror returnTypeClass, Calendar cal, TypeMirror mapValType, TypeMirror mapKeyType) { + return new CompileTimeRowToObjectMapper(keys, returnTypeClass, cal, mapValType, mapKeyType); } - public void writeObject(final Writer w, final String[] keys, final TypeMirror returnTypeMirror, final Class returnType, final Calendar cal) throws IOException { - getRowMapper(keys, returnType, cal, null, null).gen(w, returnTypeMirror.toString()); + public void writeObject(final Writer w, final String[] keys, final TypeMirror returnTypeMirror, final Calendar cal) throws IOException, ClassNotFoundException { + getRowMapper(keys, returnTypeMirror, cal, null, null).gen(w, returnTypeMirror.toString()); } - public void toObject(final Writer w, final String[] keys, final TypeMirror returnTypeMirror, final Class returnType, final Calendar cal) throws IOException { + public void toObject(final Writer w, final String[] keys, final TypeMirror returnTypeMirror, final Calendar cal) throws IOException, ClassNotFoundException { w.write("\t\t\tif(rs.next()) {\n"); - writeObject(w, keys, returnTypeMirror, returnType, cal); + writeObject(w, keys, returnTypeMirror, cal); w.write("\t\t\t\treturn ret;\n\t\t\t} else {\n\t\t\t\treturn null;\n\t\t\t}\n"); } @SuppressWarnings("unchecked") - public , E> void writeCollection(final Writer w, final String[] keys, final String returnTypeString, Class collectionType, final TypeMirror componentTypeMirror, Class componentType, int arrayMaxLength, Calendar cal) throws IOException { - collectionType = (Class) ResultSetMapper.getConcreteClass(collectionType, ArrayList.class); + public , E> void writeCollection(final Writer w, final String[] keys, final String returnTypeString, final String concreteTypeString, final TypeMirror componentTypeMirror, int arrayMaxLength, Calendar cal) throws IOException, ClassNotFoundException { w.write("\t\t\tfinal "); w.write(returnTypeString); w.write(" _colret = new "); - w.write(collectionType.getCanonicalName()); + w.write(concreteTypeString); w.write(returnTypeString.substring(returnTypeString.indexOf('<'))); w.write("();\n\t\t\twhile(rs.next()) {\n"); - writeObject(w, keys, componentTypeMirror, componentType, cal); + writeObject(w, keys, componentTypeMirror, cal); w.write("\t\t\t\t_colret.add(ret);\n\t\t\t}\n"); } - public , E> void toCollection(final Writer w, final String[] keys, final TypeMirror collectionTypeMirror, Class collectionType, final TypeMirror componentTypeMirror, Class componentType, int arrayMaxLength, Calendar cal) throws IOException { - writeCollection(w, keys, collectionTypeMirror.toString(), collectionType, componentTypeMirror, componentType, arrayMaxLength, cal); + public , E> void toCollection(final Writer w, final String[] keys, final TypeMirror collectionTypeMirror, final TypeMirror componentTypeMirror, int arrayMaxLength, Calendar cal) throws IOException, ClassNotFoundException { + final String collectionType = getConcreteClassCanonicalName(collectionTypeMirror, ArrayList.class); + writeCollection(w, keys, collectionTypeMirror.toString(), collectionType, componentTypeMirror, arrayMaxLength, cal); w.write("\t\t\treturn _colret;\n"); } - public void toArray(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, Class componentType, int arrayMaxLength, Calendar cal) throws IOException { + public void toArray(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, int arrayMaxLength, Calendar cal) throws IOException, ClassNotFoundException { final String returnTypeString = componentTypeMirror.toString(); - writeCollection(w, keys, "java.util.List<" + returnTypeString + ">", ArrayList.class, componentTypeMirror, componentType, arrayMaxLength, cal); + writeCollection(w, keys, "java.util.List<" + returnTypeString + ">", "java.util.ArrayList", componentTypeMirror, arrayMaxLength, cal); w.write("\t\t\treturn _colret.toArray(new "); w.write(returnTypeString); w.write("[_colret.size()]);\n"); } - public void toListIterator(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, Class componentType, int arrayMaxLength, Calendar cal) throws IOException { + public void toListIterator(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, int arrayMaxLength, Calendar cal) throws IOException, ClassNotFoundException { final String returnTypeString = componentTypeMirror.toString(); - writeCollection(w, keys, "java.util.List<" + returnTypeString + ">", ArrayList.class, componentTypeMirror, componentType, arrayMaxLength, cal); + writeCollection(w, keys, "java.util.List<" + returnTypeString + ">", "java.util.ArrayList", componentTypeMirror, arrayMaxLength, cal); w.write("\t\t\treturn _colret.listIterator();\n"); } - public void toIterator(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, Class componentType, int arrayMaxLength, Calendar cal) throws IOException { + public void toIterator(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, int arrayMaxLength, Calendar cal) throws IOException, ClassNotFoundException { final String returnTypeString = componentTypeMirror.toString(); - writeCollection(w, keys, "java.util.List<" + returnTypeString + ">", ArrayList.class, componentTypeMirror, componentType, arrayMaxLength, cal); + writeCollection(w, keys, "java.util.List<" + returnTypeString + ">", "java.util.ArrayList", componentTypeMirror, arrayMaxLength, cal); w.write("\t\t\treturn _colret.iterator();\n"); } @SuppressWarnings("unchecked") - public , K, E> void toMap(final Writer w, final String[] keys, final TypeMirror mapTypeMirror, Class mapType, final TypeMirror mapKeyTypeMirror, Class mapKeyType, final TypeMirror componentTypeMirror, Class componentType, int arrayMaxLength, Calendar cal) throws IOException { - mapType = (Class) ResultSetMapper.getConcreteClass(mapType, HashMap.class); + public , K, E> void toMap(final Writer w, final String[] keys, final TypeMirror mapTypeMirror, final TypeMirror mapKeyTypeMirror, final TypeMirror componentTypeMirror, int arrayMaxLength, Calendar cal) throws IOException, ClassNotFoundException { + final String mapType = getConcreteClassCanonicalName(mapTypeMirror, HashMap.class); final String returnTypeString = mapTypeMirror.toString(); w.write("\t\t\tfinal "); w.write(returnTypeString); w.write(" _colret = new "); - w.write(mapType.getCanonicalName()); + w.write(mapType); w.write(returnTypeString.substring(returnTypeString.indexOf('<'))); w.write("();\n\t\t\twhile(rs.next()) {\n"); //writeObject(w, keys, componentTypeMirror, componentType, cal); - final CompilingRowToObjectMapper rm = getRowMapper(keys, componentType, cal, null, mapKeyType); + final CompileTimeRowToObjectMapper rm = getRowMapper(keys, componentTypeMirror, cal, null, mapKeyTypeMirror); rm.gen(w, componentTypeMirror.toString()); w.write("\t\t\t\t_colret.put("); - rm.extractColumnValueString(w, 1, mapKeyType); + rm.extractColumnValueString(w, 1, mapKeyTypeMirror); w.write(", ret);\n\t\t\t}\n"); w.write("\t\t\treturn _colret;\n"); } @SuppressWarnings("unchecked") public , K, E extends Collection, C> void toMapCollection(final Writer w, final String[] keys, - final TypeMirror mapTypeMirror, Class mapType, - final TypeMirror mapKeyTypeMirror, Class mapKeyType, - final TypeMirror collectionTypeMirror, Class collectionType, - final TypeMirror componentTypeMirror, Class componentType, - int arrayMaxLength, Calendar cal) throws IOException { - mapType = (Class) ResultSetMapper.getConcreteClass(mapType, HashMap.class); - collectionType = (Class) ResultSetMapper.getConcreteClass(collectionType, ArrayList.class); + final TypeMirror mapTypeMirror, + final TypeMirror mapKeyTypeMirror, + final TypeMirror collectionTypeMirror, + final TypeMirror componentTypeMirror, + int arrayMaxLength, Calendar cal) throws IOException, ClassNotFoundException { + final String mapType = getConcreteClassCanonicalName(mapTypeMirror, HashMap.class); + final String collectionType = getConcreteClassCanonicalName(collectionTypeMirror, ArrayList.class); final String returnTypeString = mapTypeMirror.toString(); final String collectionTypeString = collectionTypeMirror.toString(); w.write("\t\t\tfinal "); w.write(returnTypeString); w.write(" _colret = new "); - w.write(mapType.getCanonicalName()); + w.write(mapType); w.write(returnTypeString.substring(returnTypeString.indexOf('<'))); w.write("();\n\t\t\twhile(rs.next()) {\n"); //writeObject(w, keys, componentTypeMirror, componentType, cal); - final CompilingRowToObjectMapper rm = getRowMapper(keys, componentType, cal, null, mapKeyType); + final CompileTimeRowToObjectMapper rm = getRowMapper(keys, componentTypeMirror, cal, null, mapKeyTypeMirror); rm.gen(w, componentTypeMirror.toString()); w.write("\t\t\t\tfinal "); w.write(mapKeyTypeMirror.toString()); w.write(" _colkey = "); - rm.extractColumnValueString(w, 1, mapKeyType); + rm.extractColumnValueString(w, 1, mapKeyTypeMirror); w.write(";\n\t\t\t\t"); w.write(collectionTypeString); w.write(" _collist = _colret.get(_colkey);\n\t\t\t\tif(_collist == null) {\n\t\t\t\t\t_collist = new "); - w.write(collectionType.getCanonicalName()); + w.write(collectionType); w.write(collectionTypeString.substring(collectionTypeString.indexOf('<'))); w.write("();\n\t\t\t\t\t_colret.put(_colkey, _collist);\n\t\t\t\t}\n\t\t\t\t_collist.add(ret);\n\t\t\t}\n\t\t\treturn _colret;\n"); } - - private static Type[] getActualTypeArguments(final List typeArguments) throws ClassNotFoundException { - final Type[] ret = new Type[typeArguments.size()]; - int x = -1; - for(final TypeMirror tm : typeArguments) - ret[++x] = typeMirrorToClass(tm); - return ret; - } - - /* - - private static Type[] getActualTypeArguments(Method m) { - return ((ParameterizedType) m.getGenericReturnType()).getActualTypeArguments(); - } - - @SuppressWarnings("unchecked") - private Method fromExecutableElement(final ExecutableElement eeMethod) throws ClassNotFoundException, NoSuchMethodException { - final Class c = Class.forName(eeMethod.getEnclosingElement().asType().toString()); - final List params = eeMethod.getParameters(); - final Class[] parameterTypes = new Class[params.size()]; - int count = -1; - for (final VariableElement param : params) { - parameterTypes[++count] = typeMirrorToClass(param.asType()); - } - return c.getDeclaredMethod(eeMethod.getSimpleName().toString(), parameterTypes); - } - - */ } diff --git a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeRowToObjectMapper.java b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeRowToObjectMapper.java new file mode 100644 index 0000000..104f219 --- /dev/null +++ b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeRowToObjectMapper.java @@ -0,0 +1,400 @@ +package com.moparisthebest.jdbc.codegen; + +import com.moparisthebest.jdbc.CompilingRowToObjectMapper; +import com.moparisthebest.jdbc.Finishable; +import com.moparisthebest.jdbc.MapperException; +import com.moparisthebest.jdbc.TypeMappingsFactory; + +import javax.lang.model.element.*; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import java.io.IOException; +import java.io.Writer; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +import java.util.regex.Matcher; + +import static com.moparisthebest.jdbc.CompilingRowToObjectMapper.escapeMapKeyString; +import static com.moparisthebest.jdbc.RowToObjectMapper._setterRegex; +import static com.moparisthebest.jdbc.codegen.JdbcMapperProcessor.typeMirrorToClass; + +/** + * Created by mopar on 6/7/17. + */ +public class CompileTimeRowToObjectMapper { + + protected static final TypeMappingsFactory _tmf = TypeMappingsFactory.getInstance(); + + final String[] keys; + + /** + * Calendar instance for date/time mappings. + */ + protected final Calendar _cal; + + /** + * Class to map ResultSet Rows to. + */ + protected final TypeMirror _returnTypeClass; + + protected final TypeMirror _mapKeyType; + + protected final int _columnCount; + + protected final boolean mapOnlySecondColumn; + + // only non-null when _returnTypeClass is an array, or a map + protected final TypeMirror componentType; + protected final boolean returnMap, resultSetConstructor; + + protected Element[] _fields = null; + protected int[] _fieldTypes; + + public CompileTimeRowToObjectMapper(final String[] keys, final TypeMirror returnTypeClass, final Calendar cal, final TypeMirror mapValType, final TypeMirror mapKeyType) { + this.keys = keys; + + _cal = cal; + _mapKeyType = mapKeyType; + + _columnCount = keys.length - 1; + + mapOnlySecondColumn = _mapKeyType != null && _columnCount == 2; + + returnMap = false;//todo: Map.class.isAssignableFrom(returnTypeClass); + if (returnMap) { + // todo: need this? _returnTypeClass = ResultSetMapper.getConcreteClass(returnTypeClass, HashMap.class); + _returnTypeClass = null; + componentType = mapValType; + resultSetConstructor = false; + } else { + _returnTypeClass = returnTypeClass; + // detect if we want an array back + componentType = returnTypeClass.getKind() == TypeKind.ARRAY ? ((ArrayType) returnTypeClass).getComponentType() : null; + + // detect if returnTypeClass has a constructor that takes a ResultSet, if so, our job couldn't be easier... + boolean resultSetConstructor = false, defaultConstructor = false; + if(_returnTypeClass.getKind() == TypeKind.DECLARED) { + final List methodsAndConstructors = ((TypeElement)((DeclaredType)_returnTypeClass).asElement()).getEnclosedElements(); + for(final Element e : methodsAndConstructors) { + if(e.getKind() == ElementKind.CONSTRUCTOR) { + final List params = ((ExecutableElement)e).getParameters(); + if(params.isEmpty()) + defaultConstructor = true; + else if(params.size() == 1 && params.get(0).asType().toString().equals("java.sql.ResultSet")) // todo: this better + resultSetConstructor = true; + } + } + } + this.resultSetConstructor = resultSetConstructor; + if(!resultSetConstructor && !defaultConstructor && _columnCount > 2 && componentType == null) + throw new RuntimeException("Exception when trying to get constructor for : "+_returnTypeClass.toString() + " Must have default no-arg constructor or one that takes a single ResultSet."); + } + } + + /** + * Build the structures necessary to do the mapping + * + * @throws SQLException on error. + */ + protected void getFieldMappings() { + + if(_returnTypeClass.getKind() != TypeKind.DECLARED) + throw new RuntimeException("_returnTypeClass " + _returnTypeClass + " not TypeKind.DECLARED ?? how??"); + + final DeclaredType declaredReturnType = (DeclaredType)_returnTypeClass; + + // added this to handle stripping '_' from keys + Map strippedKeys = new HashMap(); + for (final String key : keys) { + String strippedKey = key; + if (key != null) { + strippedKey = key.replaceAll("_", ""); + if (key.equals(strippedKey)) + continue; + strippedKeys.put(strippedKey, key); + } + } + //System.out.println("strippedKeys: "+strippedKeys); + // + // find fields or setters for return class + // + HashMap mapFields = new HashMap(_columnCount * 2); + for (int i = 1; i <= _columnCount; i++) { + mapFields.put(keys[i], null); + } + + // public methods todo: does this do super methods too? + for (Element e : ((TypeElement)declaredReturnType.asElement()).getEnclosedElements()) { + if(e.getKind() != ElementKind.METHOD) + continue; + final ExecutableElement m = (ExecutableElement)e; + //System.out.printf("method: '%s', isSetterMethod: '%s'\n", m, isSetterMethod(m)); + if (isSetterMethod(m)) { + String fieldName = m.getSimpleName().toString().substring(3).toUpperCase(); + //System.out.println("METHOD-fieldName1: "+fieldName); + if (!mapFields.containsKey(fieldName)) { + fieldName = strippedKeys.get(fieldName); + if (fieldName == null) + continue; + //System.out.println("METHOD-fieldName2: "+fieldName); + } + final Element field = mapFields.get(fieldName); + // check for overloads + if (field == null) { + mapFields.put(fieldName, m); + } else { + // todo: does this work? + // fix for 'overloaded' methods when it comes to stripped keys, we want the exact match + final String thisName = m.getSimpleName().toString().substring(3).toUpperCase(); + final String previousName = field.getSimpleName().toString().substring(3).toUpperCase(); + //System.out.printf("thisName: '%s', previousName: '%s', mapFields.containsKey(thisName): %b, strippedKeys.containsKey(previousName): %b\n", thisName, previousName, mapFields.containsKey(thisName), strippedKeys.containsKey(previousName)); + if(mapFields.containsKey(thisName) && strippedKeys.containsKey(previousName)) { + mapFields.put(fieldName, m); + } else if (!mapFields.containsKey(previousName) || !strippedKeys.containsKey(thisName)) { + throw new MapperException("Unable to choose between overloaded methods '" + m.getSimpleName().toString() + + "' and '" + field.getSimpleName().toString() + "' for field '" + fieldName + "' on the '" + _returnTypeClass.toString() + "' class. Mapping is done using " + + "a case insensitive comparison of SQL ResultSet columns to field " + + "names and public setter methods on the return class. Columns are also " + + "stripped of '_' and compared if no match is found with them."); + } + // then the 'overloaded' method is already correct + } + } + } + + // fix for 8813: include inherited and non-public fields + for (DeclaredType clazz = declaredReturnType; clazz != null && !clazz.toString().equals("java.lang.Object"); + clazz = clazz.getEnclosingType().getKind() == TypeKind.DECLARED ? (DeclaredType)clazz.getEnclosingType() : null + ) { + //System.out.println("fields in class: "+Arrays.toString(classFields)); + for (Element e : ((TypeElement)clazz.asElement()).getEnclosedElements()) { + if(e.getKind() != ElementKind.FIELD) + continue; + final VariableElement f = (VariableElement)e; + //System.out.println("f.fieldName: "+f.getSimpleName()); + if (f.getModifiers().contains(Modifier.STATIC)) continue; + if (!f.getModifiers().contains(Modifier.PUBLIC)) continue; + String fieldName = f.getSimpleName().toString().toUpperCase(); + //System.out.println("fieldName: "+fieldName); + if (!mapFields.containsKey(fieldName)) { + fieldName = strippedKeys.get(fieldName); + if (fieldName == null) + continue; + } + final Element field = mapFields.get(fieldName); + if (field == null) { + mapFields.put(fieldName, f); + } else if(field.getKind() == ElementKind.FIELD) { + // fix for 'overloaded' fields when it comes to stripped keys, we want the exact match + final String thisName = f.getSimpleName().toString().toUpperCase(); + final String previousName = field.getSimpleName().toString().toUpperCase(); + //System.out.printf("thisName: '%s', previousName: '%s', mapFields.containsKey(thisName): %b, strippedKeys.containsKey(previousName): %b\n", thisName, previousName, mapFields.containsKey(thisName), strippedKeys.containsKey(previousName)); + if(mapFields.containsKey(thisName) && strippedKeys.containsKey(previousName)) { + mapFields.put(fieldName, f); + } else if (!mapFields.containsKey(previousName) || !strippedKeys.containsKey(thisName)) { + throw new MapperException("Unable to choose between overloaded fields '" + f.getSimpleName().toString() + + "' and '" + field.getSimpleName().toString() + "' for field '" + fieldName + "' on the '" + _returnTypeClass.toString() + "' class. Mapping is done using " + + "a case insensitive comparison of SQL ResultSet columns to field " + + "names and public setter methods on the return class. Columns are also " + + "stripped of '_' and compared if no match is found with them."); + } + // then the 'overloaded' field is already correct + } + } + } + + // finally actually init the fields array + _fields = new Element[_columnCount + 1]; + _fieldTypes = new int[_columnCount + 1]; + + for (int i = 1; i < _fields.length; i++) { + Element f = mapFields.get(keys[i]); + if (f == null) { + throw new MapperException("Unable to map the SQL column '" + keys[i] + + "' to a field on the '" + _returnTypeClass.toString() + + "' class. Mapping is done using a case insensitive comparison of SQL ResultSet " + + "columns to field " + + "names and public setter methods on the return class. Columns are also " + + "stripped of '_' and compared if no match is found with them."); + } + _fields[i] = f; + if (f.getKind() == ElementKind.FIELD) { + _fieldTypes[i] = getTypeId(((VariableElement) f).asType()); + } else { + _fieldTypes[i] = getTypeId(((ExecutableElement) f).getParameters().get(0).asType()); + } + } + } + + /** + * Determine if the given method is a java bean setter method. + * @param method Method to check + * @return True if the method is a setter method. + */ + protected boolean isSetterMethod(final ExecutableElement method) { + Matcher matcher = _setterRegex.matcher(method.getSimpleName()); + if (matcher.matches()) { + + final Set modifiers = method.getModifiers(); + if (modifiers.contains(Modifier.STATIC)) return false; + if (!modifiers.contains(Modifier.PUBLIC)) return false; + if (TypeKind.VOID != method.getReturnType().getKind()) return false; + + // method parameter checks + final List params = method.getParameters(); + if (params.size() != 1) return false; + if (TypeMappingsFactory.TYPE_UNKNOWN == getTypeId(params.get(0).asType())) return false; + + return true; + } + return false; + } + + public void gen(final Appendable java, final String tType) throws IOException, ClassNotFoundException { + + if(mapOnlySecondColumn){ + java.append("final ").append(tType).append(" ret = "); + extractColumnValueString(java, 2, _returnTypeClass); + java.append(";\n"); + return; + } + + if (resultSetConstructor) { + java.append("final ").append(tType).append(" ret = new ").append(tType).append("(rs);\n"); + return; + } + + if (returnMap) // we want a map + try { + // todo: does not call getMapImplementation, I think that's fine + java.append("final ").append(tType).append(" ret = new ").append(tType).append("();\n"); + final int columnLength = _columnCount + 1; + int typeId = getTypeId(componentType); + if (componentType != null && typeId != TypeMappingsFactory.TYPE_UNKNOWN) { // we want a specific value type + for (int x = 1; x < columnLength; ++x) { + java.append("ret.put(").append(escapeMapKeyString(keys[x]).toLowerCase()).append(", "); + extractColumnValueString(java, x, typeId); + java.append(");\n"); + } + } else // we want a generic object type + for (int x = 1; x < columnLength; ++x) + java.append("ret.put(").append(escapeMapKeyString(keys[x].toLowerCase())).append(", rs.getObject(").append(String.valueOf(x)).append("));\n"); + return; + } catch (Throwable e) { + throw new MapperException(e.getClass().getName() + " when trying to create a Map from a ResultSet row" + + ", all columns must be of the map value type", e); + } + else if (componentType != null) // we want an array + try { + java.append("final ").append(tType).append(" ret = new ").append(tType.substring(0, tType.length() - 1)).append(String.valueOf(_columnCount)).append("];\n"); + final int typeId = getTypeId(componentType); + for (int x = 0; x < _columnCount; ) { + java.append("ret[").append(String.valueOf(x)).append("] = "); + extractColumnValueString(java, ++x, typeId); + java.append(";\n"); + } + return; + } catch (Throwable e) { + throw new MapperException(e.getClass().getName() + " when trying to create a " + + componentType + "[] from a ResultSet row, all columns must be of that type", e); + } + + // if the ResultSet only contains a single column we may be able to map directly + // to the return type -- if so we don't need to build any structures to support + // mapping + if (_columnCount == 1) { + + + try { + final int typeId = getTypeId(_returnTypeClass); + if (typeId != TypeMappingsFactory.TYPE_UNKNOWN) { + java.append("final ").append(tType).append(" ret = "); + extractColumnValueString(java, 1, typeId); + java.append(";\n"); + return; + } else { + // we still might want a single value (i.e. java.util.Date) + /* + Object val = extractColumnValue(1, typeId); + if (_returnTypeClass.isAssignableFrom(val.getClass())) { + return _returnTypeClass.cast(val); + } + */ + // we could actually pull from first row like above and test it first and fail now, but maybe just failing during compilation is enough? + java.append("final ").append(tType).append(" ret = (").append(tType).append(") "); + extractColumnValueString(java, 1, typeId); + java.append(";\n"); + return; + } + } catch (Exception e) { + throw new MapperException(e.getMessage(), e); + } + } + + if (_fields == null) + getFieldMappings(); + + java.append("final ").append(tType).append(" ret = new ").append(tType).append("();\n"); + + for (int i = 1; i < _fields.length; i++) { + final Element f = _fields[i]; + final boolean isField = f.getKind() == ElementKind.FIELD; + + //_args[0] = extractColumnValue(i, _fieldTypes[i]); + //System.out.printf("field: '%s' obj: '%s' fieldType: '%s'\n", _fields[i], _args[0], _fieldTypes[i]); + // custom hacked-in support for enums, can do better when we scrap org.apache.beehive.controls.system.jdbc.TypeMappingsFactory + if (_fieldTypes[i] == 0) { + /* + final Class fieldType = isField ? ((Field) f).getType() : ((Method) f).getParameterTypes()[0]; + if (Enum.class.isAssignableFrom(fieldType)) { + _args[0] = Enum.valueOf((Class) fieldType, (String) _args[0]); + if (f instanceof Field) { + // if f not accessible (but super.getFieldMappings() sets it), throw exception during compilation is fine + java.append("ret.").append(((Field) f).getName()).append(" = ").append(typeFromName(fieldType)).append(".valueOf("); + extractColumnValueString(java, i, _fieldTypes[i]); + java.append(");\n"); + } else { + java.append("ret.").append(((Method) f).getName()).append("(").append(typeFromName(fieldType)).append(".valueOf("); + extractColumnValueString(java, i, _fieldTypes[i]); + java.append("));\n"); + } + } + */ + // no for now... + } + if (isField) { + // if f not accessible (but super.getFieldMappings() sets it), throw exception during compilation is fine + java.append("ret.").append(f.getSimpleName().toString()).append(" = "); + extractColumnValueString(java, i, _fieldTypes[i]); + java.append(";\n"); + } else { + java.append("ret.").append(f.getSimpleName().toString()).append("("); + extractColumnValueString(java, i, _fieldTypes[i]); + java.append(");\n"); + } + } + // if this resultObject is Finishable, call finish() + //if (Finishable.class.isAssignableFrom(_returnTypeClass)) + //todo: java.append("ret.finish(rs);\n"); + } + + public static int getTypeId(TypeMirror classType) { + try { + return _tmf.getTypeId(typeMirrorToClass(classType)); + } catch (ClassNotFoundException e) { + return TypeMappingsFactory.TYPE_UNKNOWN; + } + } + + public static void extractColumnValueString(final Appendable java, final int index, final int resultType) throws IOException, ClassNotFoundException { + CompilingRowToObjectMapper.extractColumnValueString(java, index, resultType); + } + + public static void extractColumnValueString(final Appendable java, final int index, final TypeMirror resultType) throws IOException, ClassNotFoundException { + CompilingRowToObjectMapper.extractColumnValueString(java, index, typeMirrorToClass(resultType)); + } +} diff --git a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java index d2eb02c..3987a1c 100644 --- a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java +++ b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java @@ -555,6 +555,8 @@ public class JdbcMapperProcessor extends AbstractProcessor { } public static Class typeMirrorToClass(final TypeMirror tm) throws ClassNotFoundException { + if(tm == null) + return null; switch (tm.getKind()) { case BOOLEAN: return boolean.class; @@ -599,8 +601,7 @@ public class JdbcMapperProcessor extends AbstractProcessor { } case DECLARED: if (!((DeclaredType) tm).getTypeArguments().isEmpty()) { - final String classWithGenerics = tm.toString(); - return Class.forName(classWithGenerics.substring(0, classWithGenerics.indexOf('<'))); + return Class.forName(typeMirrorStringNoGenerics(tm)); } // fallthrough otherwise... default: @@ -608,6 +609,11 @@ public class JdbcMapperProcessor extends AbstractProcessor { } } + public static String typeMirrorStringNoGenerics(final TypeMirror tm) { + final String classWithGenerics = tm.toString(); + return classWithGenerics.substring(0, classWithGenerics.indexOf('<')); + } + public ExecutableElement getCloseMethod(final TypeElement genClass) { ExecutableElement ret = null; for (final Element methodElement : genClass.getEnclosedElements()) { diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java index ef8e0d0..134c68f 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java @@ -2,6 +2,7 @@ package com.moparisthebest.jdbc; import com.moparisthebest.classgen.Compiler; +import javax.lang.model.type.TypeMirror; import java.io.IOException; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; @@ -139,7 +140,7 @@ public class CompilingRowToObjectMapper extends RowToObjectMapper { } } - protected String escapeMapKeyString(final String s) { + public static String escapeMapKeyString(final String s) { // todo: escape key string, newlines, double quotes, backslashes... // actually it seems like those wouldn't be valid SQL column names, so we won't bother until we hear different... return '"' + s + '"'; @@ -309,7 +310,7 @@ public class CompilingRowToObjectMapper extends RowToObjectMapper { } - public void extractColumnValueString(final Appendable java, final int index, final Class resultType) throws IOException { + public static void extractColumnValueString(final Appendable java, final int index, final Class resultType) throws IOException { extractColumnValueString(java, index, _tmf.getTypeId(resultType)); } @@ -321,7 +322,7 @@ public class CompilingRowToObjectMapper extends RowToObjectMapper { * @return The extracted value * @throws java.sql.SQLException on error. */ - public void extractColumnValueString(final Appendable java, final int index, final int resultType) throws IOException { + public static void extractColumnValueString(final Appendable java, final int index, final int resultType) throws IOException { switch (resultType) { case TypeMappingsFactory.TYPE_INT: java.append("rs.getInt(").append(String.valueOf(index)).append(")"); diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java index 667f952..77d81b5 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java @@ -55,7 +55,7 @@ public class RowToObjectMapper extends AbstractRowMapper { private static final String SETTER_NAME_REGEX = "^(set)([A-Z_]\\w*+)"; protected static final TypeMappingsFactory _tmf = TypeMappingsFactory.getInstance(); - protected static final Pattern _setterRegex = Pattern.compile(SETTER_NAME_REGEX); + public static final Pattern _setterRegex = Pattern.compile(SETTER_NAME_REGEX); public static final int TYPE_BOOLEAN = _tmf.getTypeId(Boolean.TYPE);//TypeMappingsFactory.TYPE_BOOLEAN; // not public? public static final int TYPE_BOOLEAN_OBJ = _tmf.getTypeId(Boolean.class);//TypeMappingsFactory.TYPE_BOOLEAN_OBJ; // not public? diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/TypeMappingsFactory.java b/querymapper/src/main/java/com/moparisthebest/jdbc/TypeMappingsFactory.java index fa3890b..5956922 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/TypeMappingsFactory.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/TypeMappingsFactory.java @@ -311,6 +311,9 @@ public final class TypeMappingsFactory { */ public int getTypeId(Class classType) { + if(classType == null) + return TYPE_UNKNOWN; + final Class origType = classType; while (null != classType) { Integer typeObj = (Integer) _typeMap.get(classType);