Migrate JdbcMapperProcessor away from using reflection and Class objects all together...

This commit is contained in:
Travis Burtrum 2017-06-07 02:43:46 -04:00
parent a1ad929cf4
commit 783b51b810
6 changed files with 481 additions and 92 deletions

View File

@ -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 <T, E> String getConcreteClassCanonicalName(final TypeMirror returnType, final Class<E> defaultConcreteClass) {
final Set<Modifier> modifiers = ((DeclaredType) returnType).asElement().getModifiers();
if (modifiers.contains(Modifier.ABSTRACT)) { // todo: no interface?
try {
final Class<? extends T> concrete = (Class<? extends T>) ResultSetMapper.interfaceToConcrete.get(typeMirrorToClass(returnType));
return (concrete == null ? (Class<? extends T>) 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<? extends TypeMirror> typeArguments = ((DeclaredType) returnTypeMirror).getTypeArguments();
toCollection(w, keys, returnTypeMirror, (Class<? extends Collection>)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<? extends TypeMirror> 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<? extends Map>)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<? extends Map>)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<? extends TypeMirror> 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 <K, T> CompilingRowToObjectMapper<K, T> getRowMapper(final String[] keys, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
return new CompilingRowToObjectMapper<K, T>(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 <T extends Collection<E>, E> void writeCollection(final Writer w, final String[] keys, final String returnTypeString, Class<T> collectionType, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
collectionType = (Class<T>) ResultSetMapper.getConcreteClass(collectionType, ArrayList.class);
public <T extends Collection<E>, 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 <T extends Collection<E>, E> void toCollection(final Writer w, final String[] keys, final TypeMirror collectionTypeMirror, Class<T> collectionType, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
writeCollection(w, keys, collectionTypeMirror.toString(), collectionType, componentTypeMirror, componentType, arrayMaxLength, cal);
public <T extends Collection<E>, 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 <E> void toArray(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
public <E> 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 <E> void toListIterator(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
public <E> 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 <E> void toIterator(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
public <E> 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 <T extends Map<K, E>, K, E> void toMap(final Writer w, final String[] keys, final TypeMirror mapTypeMirror, Class<T> mapType, final TypeMirror mapKeyTypeMirror, Class<E> mapKeyType, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
mapType = (Class<T>) ResultSetMapper.getConcreteClass(mapType, HashMap.class);
public <T extends Map<K, E>, 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 <T extends Map<K, E>, K, E extends Collection<C>, C> void toMapCollection(final Writer w, final String[] keys,
final TypeMirror mapTypeMirror, Class<T> mapType,
final TypeMirror mapKeyTypeMirror, Class<E> mapKeyType,
final TypeMirror collectionTypeMirror, Class<E> collectionType,
final TypeMirror componentTypeMirror, Class<E> componentType,
int arrayMaxLength, Calendar cal) throws IOException {
mapType = (Class<T>) ResultSetMapper.getConcreteClass(mapType, HashMap.class);
collectionType = (Class<E>) 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<? extends TypeMirror> 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<? extends VariableElement> 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);
}
*/
}

View File

@ -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<? 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;
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<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;
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<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 {
// todo: does not call getMapImplementation, I think that's fine
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);
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<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);
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<? extends Enum>) 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));
}
}

View File

@ -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()) {

View File

@ -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<K, T> extends RowToObjectMapper<K, T> {
}
}
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<K, T> extends RowToObjectMapper<K, T> {
}
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<K, T> extends RowToObjectMapper<K, T> {
* @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(")");

View File

@ -55,7 +55,7 @@ public class RowToObjectMapper<K, T> extends AbstractRowMapper<K, T> {
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?

View File

@ -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);