package com.w11k.lsql;
import com.google.common.collect.Maps;
import com.w11k.lsql.converter.Converter;
import org.apache.commons.beanutils.PropertyUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.util.Map;
import static com.w11k.lsql.utils.JavaClassUtils.convertPrimitiveClassToWrapperClass;
public class PojoMapper<T> {
private final static Map<Class<?>, PojoMapper<?>> CACHE = Maps.newHashMap();
@SuppressWarnings("unchecked")
public static <T> PojoMapper<T> getFor(Class<T> clazz) {
if (!CACHE.containsKey(clazz)) {
CACHE.put(clazz, new PojoMapper<Object>((Class<Object>) clazz));
}
return (PojoMapper<T>) CACHE.get(clazz);
}
private final Class<T> pojoClass;
private final Map<String, PropertyDescriptor> propertyDescriptors = Maps.newHashMap();
private final Constructor<T> constructor;
public PojoMapper(Class<T> pojoClass) {
// Find constructor
this.constructor = getConstructor(pojoClass);
this.pojoClass = pojoClass;
// Extract property names
PropertyDescriptor[] descs = PropertyUtils.getPropertyDescriptors(pojoClass);
for (PropertyDescriptor desc : descs) {
Class<?> declaringClass = desc.getReadMethod().getDeclaringClass();
if (declaringClass.equals(Object.class)) {
continue;
}
setPropertyAccessible(desc);
this.propertyDescriptors.put(desc.getName(), desc);
}
}
public T newInstance() {
try {
return this.constructor.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Object getValue(T instance, String fieldName) {
PropertyDescriptor descriptor = this.propertyDescriptors.get(fieldName);
try {
return descriptor.getReadMethod().invoke(instance);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void setValue(T instance, String fieldName, Object value) {
PropertyDescriptor descriptor = this.propertyDescriptors.get(fieldName);
try {
descriptor.getWriteMethod().invoke(instance, value);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Class<?> getTypeOfField(String fieldName) {
return this.propertyDescriptors.get(fieldName).getPropertyType();
}
public Row pojoToRow(T pojo) {
try {
Row row = new Row();
for (PropertyDescriptor desc : this.propertyDescriptors.values()) {
Object value = desc.getReadMethod().invoke(pojo);
row.put(desc.getName(), value);
}
return row;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public T rowToPojo(Row row) {
T pojo = newInstance();
assignRowToPojo(row, pojo);
return pojo;
}
public void assignRowToPojo(Row row, T pojo) {
for (PropertyDescriptor desc : this.propertyDescriptors.values()) {
Object value = row.get(desc.getName());
setValue(pojo, desc.getName(), value);
}
}
public void checkConformity(Map<String, Converter> converters) {
String logClassName = this.pojoClass.getCanonicalName();
// Check SQL -> Java
for (String columnName : converters.keySet()) {
Converter converter = converters.get(columnName);
// missing field
PropertyDescriptor descriptor = this.propertyDescriptors.get(columnName);
if (descriptor == null) {
throw new IllegalArgumentException(
logClassName + " is missing field '" + columnName + "' of type '" + converter.getJavaType().getCanonicalName() + "'"
);
}
// wrong type in field
else if (!convertPrimitiveClassToWrapperClass(
descriptor.getPropertyType()).isAssignableFrom(converter.getJavaType())) {
throw new IllegalArgumentException(
"Field " + logClassName + "#" + columnName + " has the wrong type: '" + descriptor.getPropertyType().getCanonicalName() + "'. Expected: '" + converter.getJavaType().getCanonicalName() + "'"
);
}
}
// Check Java -> SQL
for (String field : this.propertyDescriptors.keySet()) {
if (converters.keySet().contains(field)) {
continue;
}
// superfluous field
if (!converters.keySet().contains(field)) {
throw new IllegalArgumentException(
logClassName + " has superfluous field '" + field + "'"
);
}
}
}
private Constructor<T> getConstructor(Class<T> pojoClass) {
try {
Constructor<T> constructor = pojoClass.getConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private void setPropertyAccessible(PropertyDescriptor desc) {
try {
if (!desc.getReadMethod().isAccessible()) {
desc.getReadMethod().setAccessible(true);
}
if (!desc.getWriteMethod().isAccessible()) {
desc.getWriteMethod().setAccessible(true);
}
} catch (SecurityException e) {
throw new RuntimeException("Property read/write methods must be accessible", e);
}
}
}