package com.dieselpoint.norm.sqlmakers;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import com.dieselpoint.norm.DbException;
import com.dieselpoint.norm.serialize.DbSerializer;
/**
* Provides means of reading and writing properties in a pojo.
*/
public class StandardPojoInfo implements PojoInfo {
/*
* annotations recognized: @ Id, @ GeneratedValue @ Transient @ Table @ Column @ DbSerializer @ Enumerated
*/
LinkedHashMap<String, Property> propertyMap = new LinkedHashMap<String, Property>();
String table;
String primaryKeyName;
String generatedColumnName;
String insertSql;
int insertSqlArgCount;
String [] insertColumnNames;
String upsertSql;
int upsertSqlArgCount;
String [] upsertColumnNames;
String updateSql;
String[] updateColumnNames;
int updateSqlArgCount;
String selectColumns;
public StandardPojoInfo(Class<?> clazz) {
try {
if (Map.class.isAssignableFrom(clazz)) {
//leave properties empty
} else {
populateProperties(clazz);
}
Table annot = (Table) clazz.getAnnotation(Table.class);
if (annot != null) {
table = annot.name();
} else {
table = clazz.getSimpleName();
}
} catch (Throwable t) {
throw new DbException(t);
}
}
private void populateProperties(Class<?> clazz) throws IntrospectionException, InstantiationException, IllegalAccessException {
for (Field field : clazz.getFields()) {
int modifiers = field.getModifiers();
if (Modifier.isPublic(modifiers)) {
if (Modifier.isStatic(modifiers)
|| Modifier.isFinal(modifiers)) {
continue;
}
if (field.getAnnotation(Transient.class) != null) {
continue;
}
Property prop = new Property();
prop.name = field.getName();
prop.field = field;
prop.dataType = field.getType();
applyAnnotations(prop, field);
propertyMap.put(prop.name, prop);
}
}
BeanInfo beanInfo = Introspector.getBeanInfo(clazz, Object.class);
PropertyDescriptor[] descriptors = beanInfo
.getPropertyDescriptors();
for (PropertyDescriptor descriptor : descriptors) {
Method readMethod = descriptor.getReadMethod();
if (readMethod == null) {
continue;
}
if (readMethod.getAnnotation(Transient.class) != null) {
continue;
}
Property prop = new Property();
prop.name = descriptor.getName();
prop.readMethod = readMethod;
prop.writeMethod = descriptor.getWriteMethod();
prop.dataType = descriptor.getPropertyType();
applyAnnotations(prop, prop.readMethod);
propertyMap.put(prop.name, prop);
}
}
/**
* Apply the annotations on the field or getter method to the property.
* @throws IllegalAccessException
* @throws InstantiationException
*/
private void applyAnnotations(Property prop, AnnotatedElement ae) throws InstantiationException, IllegalAccessException {
Column col = ae.getAnnotation(Column.class);
if (col != null) {
String name = col.name().trim();
if (name.length() > 0) {
prop.name = name;
}
prop.columnAnnotation = col;
}
if (ae.getAnnotation(Id.class) != null) {
prop.isPrimaryKey = true;
primaryKeyName = prop.name;
}
if (ae.getAnnotation(GeneratedValue.class) != null) {
generatedColumnName = prop.name;
prop.isGenerated = true;
}
if (prop.dataType.isEnum()) {
prop.isEnumField = true;
prop.enumClass = (Class<Enum>) prop.dataType;
/* We default to STRING enum type. Can be overriden with @Enumerated annotation */
prop.enumType = EnumType.STRING;
if (ae.getAnnotation(Enumerated.class) != null) {
prop.enumType = ae.getAnnotation(Enumerated.class).value();
}
}
DbSerializer sc = ae.getAnnotation(DbSerializer.class);
if (sc != null) {
prop.serializer = sc.value().newInstance();
}
}
/*
private Method getMethod(Method meth, String propertyName, Property pair) {
if (meth == null) {
return null;
}
if (meth.getAnnotation(Transient.class) != null) {
return null;
}
if (meth.getAnnotation(Id.class) != null) {
this.primaryKeyName = propertyName;
pair.isPrimaryKey = true;
}
if (meth.getAnnotation(GeneratedValue.class) != null) {
this.generatedColumnName = propertyName;
pair.isGenerated = true;
}
return meth;
}
*/
public Object getValue(Object pojo, String name) {
try {
Property prop = propertyMap.get(name);
if (prop == null) {
throw new DbException("No such field: " + name);
}
Object value = null;
if (prop.readMethod != null) {
value = prop.readMethod.invoke(pojo);
} else if (prop.field != null) {
value = prop.field.get(pojo);
}
if (value != null) {
if (prop.serializer != null) {
value = prop.serializer.serialize(value);
} else if (prop.isEnumField) {
// handle enums according to selected enum type
if (prop.enumType == EnumType.ORDINAL) {
value = ((Enum) value).ordinal();
}
// EnumType.STRING and others (if present in the future)
else {
value = value.toString();
}
}
}
return value;
} catch (Throwable t) {
throw new DbException(t);
}
}
public void putValue(Object pojo, String name, Object value) {
Property prop = propertyMap.get(name);
if (prop == null) {
throw new DbException("No such field: " + name);
}
if (value != null) {
if (prop.serializer != null) {
value = prop.serializer.deserialize((String) value, prop.dataType);
} else if (prop.isEnumField) {
value = getEnumConst(prop.enumClass, prop.enumType, value);
}
}
if (prop.writeMethod != null) {
try {
prop.writeMethod.invoke(pojo, value);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new DbException("Could not write value into pojo. Property: " + prop.name + " method: "
+ prop.writeMethod.toString() + " value: " + value, e);
}
return;
}
if (prop.field != null) {
try {
prop.field.set(pojo, value);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new DbException("Could not set value into pojo. Field: " + prop.field.toString() + " value: " + value, e);
}
return;
}
}
/**
* Convert a string to an enum const of the appropriate class.
*/
private <T extends Enum<T>> Object getEnumConst(Class<T> enumType, EnumType type, Object value) {
String str = value.toString();
if (type == EnumType.ORDINAL) {
Integer ordinalValue = (Integer) value;
if (ordinalValue < 0 || ordinalValue >= enumType.getEnumConstants().length) {
throw new DbException("Invalid ordinal number " + ordinalValue + " for enum class " + enumType.getCanonicalName());
}
return enumType.getEnumConstants()[ordinalValue];
}
else {
for (T e: enumType.getEnumConstants()) {
if (str.equals(e.toString())) {
return e;
}
}
throw new DbException("Enum value does not exist. value:" + str);
}
}
@Override
public Property getGeneratedColumnProperty() {
return propertyMap.get(generatedColumnName);
}
}