diff --git a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeRowToObjectMapper.java b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeRowToObjectMapper.java index 5180977..955c9a1 100644 --- a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeRowToObjectMapper.java +++ b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeRowToObjectMapper.java @@ -49,7 +49,8 @@ public class CompileTimeRowToObjectMapper { protected final boolean returnMap, resultSetConstructor; protected Element[] _fields = null; - protected int[] _fieldTypes; + protected int[] _fieldTypes, _fieldOrder; + protected String[] _fieldClasses; protected final ReflectionFields reflectionFields; @@ -78,21 +79,62 @@ public class CompileTimeRowToObjectMapper { 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; + boolean resultSetConstructor = false, defaultConstructor = false, paramConstructor = false; if(_returnTypeClass.getKind() == TypeKind.DECLARED) { + Map strippedKeys = null; final List methodsAndConstructors = ((TypeElement)((DeclaredType)_returnTypeClass).asElement()).getEnclosedElements(); + + /* + // uncomment this to show difference between java 1.8 with -parameters and not, prints this without -parameters (javac 1.6 gets this correct also): + // methodsAndConstructors: FieldPerson(): '', FieldPerson(long,java.util.Date,java.lang.String,java.lang.String): 'long PERSONNO, java.util.Date BIRTHDATE, java.lang.String FIRSTNAME, java.lang.String LASTNAME', FieldPerson(com.moparisthebest.jdbc.dto.Person): 'com.moparisthebest.jdbc.dto.Person PERSON' + // but javac 1.8 prints this with -parameters (wrongly): + // methodsAndConstructors: FieldPerson(): '', FieldPerson(long,java.util.Date,java.lang.String,java.lang.String): 'long PERSONNO, java.util.Date FIRSTNAME, java.lang.String LASTNAME, java.lang.String ARG3', FieldPerson(com.moparisthebest.jdbc.dto.Person): 'com.moparisthebest.jdbc.dto.Person PERSON' + if(_returnTypeClass.toString().equals("com.moparisthebest.jdbc.dto.FieldPerson")) + throw new RuntimeException("methodsAndConstructors: " + methodsAndConstructors.stream().filter(e -> e.getKind() == ElementKind.CONSTRUCTOR && e.getModifiers().contains(Modifier.PUBLIC)).map(e -> e.toString() + + ": '" + ((ExecutableElement)e).getParameters().stream().map(param -> param.asType() + " " + param.getSimpleName().toString().toUpperCase()).collect(java.util.stream.Collectors.joining(", ")) + "'" + ).collect(java.util.stream.Collectors.joining(", "))); + */ + outer: for(final Element e : methodsAndConstructors) { - if(e.getKind() == ElementKind.CONSTRUCTOR) { + if(e.getKind() == ElementKind.CONSTRUCTOR && e.getModifiers().contains(Modifier.PUBLIC)) { final List params = ((ExecutableElement)e).getParameters(); if(params.isEmpty()) defaultConstructor = true; else if(params.size() == 1 && rsm.types.isSameType(params.get(0).asType(), rsm.resultSetType)) resultSetConstructor = true; + else if(params.size() == _columnCount) { + // maybe we want to call the constructor, if the names line up + if(strippedKeys == null) { + strippedKeys = new HashMap(keys.length * 2); + for (int x = 1; x <= _columnCount; ++x) { + final String key = keys[x]; + strippedKeys.put(key, x); + strippedKeys.put(key.replaceAll("_", ""), x); + } + _fieldOrder = new int[keys.length]; + _fieldTypes = new int[keys.length]; + _fieldClasses = new String[keys.length]; + } + int count = 0; + for(final VariableElement param : params) { + final Integer index = strippedKeys.get(param.getSimpleName().toString().toUpperCase()); + if(index == null) + continue outer; + _fieldOrder[++count] = index; + _fieldTypes[count] = getTypeId(param.asType()); + if(_fieldTypes[count] == TypeMappingsFactory.TYPE_ENUM) { + _fieldClasses[count] = param.asType().toString(); + } + } + paramConstructor = true; + } } } } + if(!paramConstructor) + _fieldOrder = null; // didn't successfully finish this.resultSetConstructor = resultSetConstructor; - if(!resultSetConstructor && !defaultConstructor && _columnCount > 2 && componentType == null) + if(!resultSetConstructor && !defaultConstructor && !paramConstructor && _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."); } } @@ -270,6 +312,17 @@ public class CompileTimeRowToObjectMapper { return; } + if(_fieldOrder != null) { + java.append("final ").append(tType).append(" ret = new ").append(tType).append("(\n"); + for(int x = 1; x <= _columnCount; ++x) { + extractColumnValueString(java, _fieldOrder[x], _fieldTypes[x], _fieldClasses[x]); + if(x != _columnCount) + java.append(",\n"); + } + java.append(");\n"); + return; + } + if (returnMap) // we want a map try { java.append("final ").append(tType).append(" ret = new ").append(tType).append("();\n"); diff --git a/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java b/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java index 921799d..79d0dde 100644 --- a/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java +++ b/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java @@ -44,12 +44,12 @@ public interface PersonDAO extends Closeable { @JdbcMapper.SQL("SELECT first_name FROM person WHERE person_no = {personNo}") String getFirstName(long personNo) throws SQLException; - @JdbcMapper.SQL("SELECT first_name, last_name, birth_date FROM person WHERE person_no = {personNo}") - FieldPerson getPerson(long personNo, Calendar cal) throws SQLException; - @JdbcMapper.SQL(value = "SELECT person_no, first_name, last_name, birth_date FROM person WHERE person_no = {personNo}") FieldPerson getPerson(long personNo) throws SQLException; + @JdbcMapper.SQL("SELECT first_name, last_name, birth_date FROM person WHERE person_no = {personNo}") + FieldPerson getPerson(long personNo, Calendar cal) throws SQLException; + @JdbcMapper.SQL("SELECT first_name, last_name FROM person WHERE last_name = {lastName}") List getPeople(String lastName) throws SQLException; diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java index 4ca5be3..1ae4eb3 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java @@ -191,7 +191,8 @@ public class RowToObjectMapper extends AbstractRowMapper { } } //IFJAVA8_END - if(constructor == null) + if(constructor == null) { + _fieldOrder = null; // we didn't complete this... try { constructor = _returnTypeClass.getDeclaredConstructor(); if (!constructor.isAccessible()) @@ -199,9 +200,10 @@ public class RowToObjectMapper extends AbstractRowMapper { } catch (Throwable e1) { // if column count is 2 or less, it might map directly to a type like a Long or something, or be a map which does // or if componentType is non-null, then we want an array like Long[] or String[] - if(_columnCount > 2 && componentType == null) - throw new MapperException("Exception when trying to get constructor for : "+_returnTypeClass.getName() + " Must have default no-arg constructor or one that takes a single ResultSet.", e1); + if (_columnCount > 2 && componentType == null) + throw new MapperException("Exception when trying to get constructor for : " + _returnTypeClass.getName() + " Must have default no-arg constructor or one that takes a single ResultSet.", e1); } + } } this.resultSetConstructor = resultSetConstructor; this.constructor = constructor;