2017-06-07 02:43:46 -04:00
package com.moparisthebest.jdbc.codegen ;
import com.moparisthebest.jdbc.CompilingRowToObjectMapper ;
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.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 ( ) ;
2017-06-07 19:15:00 -04:00
protected final CompileTimeResultSetMapper rsm ;
protected final String [ ] keys ;
2017-06-07 02:43:46 -04:00
/ * *
* Calendar instance for date / time mappings .
* /
2017-06-12 19:24:04 -04:00
protected final String _calendarName , _resultSetName ;
2017-06-07 02:43:46 -04:00
/ * *
* 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 ;
2017-06-19 00:34:23 -04:00
protected final ReflectionFields reflectionFields ;
public CompileTimeRowToObjectMapper ( final CompileTimeResultSetMapper rsm , final String [ ] keys , final TypeMirror returnTypeClass , final String resultSetName , final String calendarName , final TypeMirror mapValType , final TypeMirror mapKeyType , final ReflectionFields reflectionFields ) {
2017-06-07 19:15:00 -04:00
this . rsm = rsm ;
2017-06-07 02:43:46 -04:00
this . keys = keys ;
2017-06-19 00:34:23 -04:00
this . reflectionFields = reflectionFields ;
2017-06-07 02:43:46 -04:00
2017-06-07 20:30:58 -04:00
_calendarName = calendarName ;
2017-06-12 19:24:04 -04:00
_resultSetName = resultSetName ;
2017-06-07 02:43:46 -04:00
_mapKeyType = mapKeyType ;
_columnCount = keys . length - 1 ;
mapOnlySecondColumn = _mapKeyType ! = null & & _columnCount = = 2 ;
2017-06-07 19:15:00 -04:00
returnMap = rsm . types . isAssignable ( returnTypeClass , rsm . mapType ) ;
2017-06-07 02:43:46 -04:00
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 < ? extends Element > methodsAndConstructors = ( ( TypeElement ) ( ( DeclaredType ) _returnTypeClass ) . asElement ( ) ) . getEnclosedElements ( ) ;
for ( final Element e : methodsAndConstructors ) {
if ( e . getKind ( ) = = ElementKind . CONSTRUCTOR ) {
final List < ? extends VariableElement > params = ( ( ExecutableElement ) e ) . getParameters ( ) ;
if ( params . isEmpty ( ) )
defaultConstructor = true ;
2017-06-07 19:15:00 -04:00
else if ( params . size ( ) = = 1 & & rsm . types . isSameType ( params . get ( 0 ) . asType ( ) , rsm . resultSetType ) )
2017-06-07 02:43:46 -04:00
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 < String , String > strippedKeys = new HashMap < String , String > ( ) ;
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 < String , Element > mapFields = new HashMap < String , Element > ( _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 ;
2017-06-19 00:34:23 -04:00
//if (reflectionFields == null && !f.getModifiers().contains(Modifier.PUBLIC)) continue;
2017-06-07 02:43:46 -04:00
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 < Modifier > 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 < ? extends VariableElement > 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 {
java . append ( " final " ) . append ( tType ) . append ( " <String, Object> ret = new " ) . append ( tType ) . append ( " <String, Object>(); \ n " ) ;
final int columnLength = _columnCount + 1 ;
int typeId = getTypeId ( componentType ) ;
2017-06-13 21:42:32 -04:00
final String enumName = componentType . toString ( ) ;
2017-06-07 02:43:46 -04:00
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 ( " , " ) ;
2017-06-13 21:42:32 -04:00
extractColumnValueString ( java , x , typeId , enumName ) ;
2017-06-07 02:43:46 -04:00
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<String, "
+ ( componentType = = null ? " java.lang.Object " : componentType . toString ( ) ) + " > 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 ) ;
2017-06-13 21:42:32 -04:00
final String enumName = componentType . toString ( ) ;
2017-06-07 02:43:46 -04:00
for ( int x = 0 ; x < _columnCount ; ) {
java . append ( " ret[ " ) . append ( String . valueOf ( x ) ) . append ( " ] = " ) ;
2017-06-13 21:42:32 -04:00
extractColumnValueString ( java , + + x , typeId , enumName ) ;
2017-06-07 02:43:46 -04:00
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 = " ) ;
2017-06-13 21:42:32 -04:00
extractColumnValueString ( java , 1 , typeId , _returnTypeClass . toString ( ) ) ;
2017-06-07 02:43:46 -04:00
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 ( " ) " ) ;
2017-06-13 21:42:32 -04:00
extractColumnValueString ( java , 1 , typeId , _returnTypeClass . toString ( ) ) ;
2017-06-07 02:43:46 -04:00
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 ;
2017-06-13 21:42:32 -04:00
String enumName = null ;
if ( _fieldTypes [ i ] = = TypeMappingsFactory . TYPE_ENUM ) {
if ( f . getKind ( ) = = ElementKind . FIELD ) {
enumName = ( ( VariableElement ) f ) . asType ( ) . toString ( ) ;
} else {
enumName = ( ( ExecutableElement ) f ) . getParameters ( ) . get ( 0 ) . asType ( ) . toString ( ) ;
2017-06-07 02:43:46 -04:00
}
}
if ( isField ) {
// if f not accessible (but super.getFieldMappings() sets it), throw exception during compilation is fine
2017-06-19 00:34:23 -04:00
final Set < Modifier > mods = reflectionFields = = null ? null : f . getModifiers ( ) ;
if ( mods ! = null & & ( mods . contains ( Modifier . PRIVATE ) | | mods . contains ( Modifier . PROTECTED ) | | mods . contains ( Modifier . FINAL ) ) ) {
java . append ( " com.moparisthebest.jdbc.util.ReflectionUtil.setValue(_fields[ " ) . append ( String . valueOf ( ( reflectionFields . addGetIndex ( ( VariableElement ) f ) ) ) ) . append ( " ], ret, " ) ;
extractColumnValueString ( java , i , _fieldTypes [ i ] , enumName ) ;
java . append ( " ); \ n " ) ;
} else {
java . append ( " ret. " ) . append ( f . getSimpleName ( ) . toString ( ) ) . append ( " = " ) ;
extractColumnValueString ( java , i , _fieldTypes [ i ] , enumName ) ;
java . append ( " ; \ n " ) ;
}
2017-06-07 02:43:46 -04:00
} else {
java . append ( " ret. " ) . append ( f . getSimpleName ( ) . toString ( ) ) . append ( " ( " ) ;
2017-06-13 21:42:32 -04:00
extractColumnValueString ( java , i , _fieldTypes [ i ] , enumName ) ;
2017-06-07 02:43:46 -04:00
java . append ( " ); \ n " ) ;
}
}
// if this resultObject is Finishable, call finish()
2017-06-07 19:15:00 -04:00
if ( rsm . types . isAssignable ( _returnTypeClass , rsm . finishableType ) )
java . append ( " ret.finish(rs); \ n " ) ;
2017-06-07 02:43:46 -04:00
}
public static int getTypeId ( TypeMirror classType ) {
try {
return _tmf . getTypeId ( typeMirrorToClass ( classType ) ) ;
} catch ( ClassNotFoundException e ) {
2017-06-13 21:42:32 -04:00
// todo: what about enums?
2017-06-07 02:43:46 -04:00
return TypeMappingsFactory . TYPE_UNKNOWN ;
}
}
2017-06-13 21:42:32 -04:00
public void extractColumnValueString ( final Appendable java , final int index , final int resultType , final String enumName ) throws IOException , ClassNotFoundException {
CompilingRowToObjectMapper . extractColumnValueString ( java , index , resultType , enumName , _resultSetName , _calendarName ) ;
2017-06-07 02:43:46 -04:00
}
2017-06-07 03:00:29 -04:00
public void extractColumnValueString ( final Appendable java , final int index , final TypeMirror resultType ) throws IOException , ClassNotFoundException {
2017-06-13 21:42:32 -04:00
CompilingRowToObjectMapper . extractColumnValueString ( java , index , getTypeId ( resultType ) , resultType . toString ( ) , _resultSetName , _calendarName ) ;
2017-06-07 02:43:46 -04:00
}
}