package com.brightgenerous.orm.mapper;
import static com.brightgenerous.commons.StringConvertUtils.*;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Blob;
import java.sql.Clob;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.brightgenerous.commons.EqualsUtils;
import com.brightgenerous.commons.HashCodeUtils;
import com.brightgenerous.commons.ObjectUtils;
import com.brightgenerous.commons.StringUtils;
import com.brightgenerous.commons.ToStringUtils;
import com.brightgenerous.lang.Args;
import com.brightgenerous.orm.DefaultIfNull;
import com.brightgenerous.orm.Ignore;
import com.brightgenerous.orm.mapper.TableMapper.Flag;
public class MapperUtils {
private MapperUtils() {
}
public static void define(TableDefines defines, Register register, String table,
PropertyReference... propertyReferences) {
define(defines, register, new String[] { table }, propertyReferences);
}
public static void define(TableDefines defines, Register register, String[] tables,
PropertyReference... propertyReferences) {
Args.notNull(defines, "defines");
Args.notNull(register, "register");
Args.notEmpty(tables, "tables");
for (String table : tables) {
if (StringUtils.isEmpty(table)) {
throw new IllegalStateException("The tables has empty table value.");
}
if (defines.containsKey(table)) {
throw new IllegalStateException(String.format("already defined for %s", table));
}
TableMapper tableMapper = register.getTableMapper(table);
if (tableMapper == null) {
throw new IllegalStateException(
String.format("not found TableMapper for %s", table));
}
TableDefine define;
{
Map<String, TableMapper> ptms = new HashMap<>();
if ((propertyReferences != null) && (0 < propertyReferences.length)) {
for (PropertyReference pr : propertyReferences) {
if (pr == null) {
throw new IllegalStateException(
"The propertyReferences has null element.");
}
String p = pr.property();
String r = pr.reference();
if (register.checkStrict()) {
checkProperty(p);
}
TableMapper tm = register.getTableMapper(r);
if (tm == null) {
throw new IllegalStateException(String.format(
"not found TableMapper for %s", r));
}
if (ptms.containsKey(p)) {
throw new IllegalStateException(String.format(
"duplicate property %s with reference %s.", p, r));
}
ptms.put(p, TableMapper.createAlt(tm, p));
}
}
define = new TableDefine(tableMapper, ptms);
}
defines.put(table, define);
}
}
public static void load(Register register, Class<?> clazz, String table) {
load(register, clazz, table, (FieldColumn[]) null);
}
public static void load(Register register, Class<?> clazz, String table,
LinkedHashSet<Entry<String[], Class<?>>> primarys) {
load(register, clazz, table, (FieldColumn[]) null, primarys);
}
public static void load(Register register, Class<?> clazz, String table,
FieldColumn[] fieldColumns) {
load(register, clazz, table, fieldColumns, null);
}
public static void load(Register register, Class<?> clazz, String table,
FieldColumn[] fieldColumns, LinkedHashSet<Entry<String[], Class<?>>> primarys) {
load(register, clazz, table, null, fieldColumns, primarys);
}
public static void load(Register register, Class<?> clazz, String table, String[] aliases) {
load(register, clazz, table, aliases, (FieldColumn[]) null);
}
public static void load(Register register, Class<?> clazz, String table, String[] aliases,
LinkedHashSet<Entry<String[], Class<?>>> primarys) {
load(register, clazz, table, aliases, null, primarys);
}
public static void load(Register register, Class<?> clazz, String table, String[] aliases,
FieldColumn[] fieldColumns) {
load(register, clazz, table, aliases, fieldColumns, null);
}
public static void load(Register register, Class<?> clazz, String table, String[] aliases,
FieldColumn[] fieldColumns, LinkedHashSet<Entry<String[], Class<?>>> primarys) {
Args.notNull(register, "register");
Args.notNull(clazz, "clazz");
Args.notNull(table, "table");
LinkedHashMap<String, ColumnDefine> fcs = new LinkedHashMap<>();
{
for (Field field : clazz.getDeclaredFields()) {
if (isFieldColumn(field, register.getTypes())) {
String f = field.getName();
String c = toSnake(f, register.fieldToSnakeColumn());
if (fcs.containsKey(f)) {
throw new IllegalStateException(String.format(
"duplicate field %s with column %s.", f, c));
}
if (register.checkStrict()) {
checkFieldName(f);
checkColumnName(c);
}
fcs.put(f, new ColumnDefine(c, field.getType(), f, enableSelect(field),
enableInsert(field), enableUpdate(field), defaultIfNull(field)));
}
}
for (Field field : clazz.getFields()) {
if (isFieldColumn(field, register.getTypes())) {
String f = field.getName();
String c = toSnake(f, register.fieldToSnakeColumn());
if (fcs.containsKey(f)) {
throw new IllegalStateException(String.format(
"duplicate field %s with column %s.", f, c));
}
if (register.checkStrict()) {
checkFieldName(f);
checkColumnName(c);
}
fcs.put(f, new ColumnDefine(c, field.getType(), f, enableSelect(field),
enableInsert(field), enableUpdate(field), defaultIfNull(field)));
}
}
if ((fieldColumns != null) && (0 < fieldColumns.length)) {
for (FieldColumn fc : fieldColumns) {
if (fc == null) {
throw new IllegalStateException("The fieldColumns has null element.");
}
String f = fc.field();
String c = fc.column();
if (c == null) {
c = toSnake(MapperUtils.spritField(f), register.fieldToSnakeColumn());
}
if (!fc.override() && fcs.containsKey(f)) {
throw new IllegalStateException(String.format(
"duplicate field %s with column %s.", f, c));
}
String[] propertys = f.split("\\."); // argument is "regex". @see String#split(String)
Class<?> type = checkPropertysField(clazz, propertys, register.getTypes());
if (register.checkStrict()) {
checkFieldName(f);
checkColumnName(c);
}
Ignore.Type it = fc.ignore();
fcs.put(f, new ColumnDefine(c, type, propertys, enableSelect(it),
enableInsert(it), enableUpdate(it), fc.defaultIfNull()));
}
}
}
LinkedHashMap<String, ColumnDefine> ps = new LinkedHashMap<>();
if ((primarys != null) && !primarys.isEmpty()) {
for (Entry<String[], Class<?>> e : primarys) {
String[] k = e.getKey();
if (ObjectUtils.isNoSize(k)) {
throw new IllegalStateException("The primarys has empty propertys.");
}
Class<?> v = e.getValue();
if (v == null) {
throw new IllegalStateException("The primarys has null Class.");
}
inner: {
for (Entry<String, ColumnDefine> fc : fcs.entrySet()) {
ColumnDefine cd = fc.getValue();
if (Arrays.equals(k, cd.getPropertys())) {
Class<?> t = cd.getType();
if (!t.isAssignableFrom(v) && !v.isAssignableFrom(t)) {
throw new IllegalStateException();
}
ps.put(fc.getKey(), cd);
break inner;
}
}
throw new IllegalStateException();
}
}
}
putTableMapper(register, clazz, table, false, ps, fcs);
if ((aliases != null) && (0 < aliases.length)) {
for (String alias : aliases) {
if (StringUtils.isEmpty(alias)) {
throw new IllegalStateException("The aliases has empty alias value.");
}
putTableMapper(register, clazz, alias, true, ps, fcs);
}
}
}
private static void putTableMapper(Register register, Class<?> clazz, String table,
boolean alias, LinkedHashMap<String, ColumnDefine> primarys,
LinkedHashMap<String, ColumnDefine> fcs) {
if (register.checkStrict()) {
checkTableName(table);
}
if (register.getTableMapper(table) != null) {
throw new IllegalStateException(String.format("already registed for %s", table));
}
register.putTableMapper(table, new TableMapper(clazz, alias, table, primarys, fcs));
}
private static void checkProperty(String property) {
if (!property.matches("^[A-Za-z_]+[0-9A-Za-z_.]*$")) {
throw new IllegalStateException(String.format("The property illegal string %s",
property));
}
}
private static Class<?> checkPropertysField(Class<?> clazz, String[] propertys,
Set<Class<?>> types) {
Class<?> clz = clazz;
for (String property : propertys) {
Field fld;
try {
fld = clz.getDeclaredField(property);
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalStateException(String.format(
"occured exception, field %s in %s at part %s.",
Arrays.toString(propertys), clazz.getName(), property));
}
clz = fld.getType();
}
if (!isFieldColumn(clz, types)) {
throw new IllegalStateException(String.format("The field %s illegal type %s",
Arrays.toString(propertys), clz.getName()));
}
return clz;
}
private static void checkFieldName(String field) {
if (!field.matches("^[A-Za-z_]+[0-9A-Za-z_.]*$")) {
throw new IllegalStateException(String.format("The field illegal string %s", field));
}
}
private static void checkTableName(String table) {
if (!table.matches("^[A-Za-z_]+[0-9A-Za-z_]*$")) {
throw new IllegalStateException(String.format("The table illegal string %s", table));
}
}
private static void checkColumnName(String column) {
if (!column.matches("^[A-Za-z_]+[0-9A-Za-z_]*$")) {
throw new IllegalStateException(String.format("The column illegal string %s", column));
}
}
private static boolean isFieldColumn(Field field, Set<Class<?>> types) {
if (field == null) {
return false;
}
Class<?> type = field.getType();
if (Modifier.isStatic(field.getModifiers())) {
return false;
}
if (Void.class.isAssignableFrom(type)) {
return false;
}
if (ignoreAll(field)) {
return false;
}
return isFieldColumn(type, types);
}
private static boolean isFieldColumn(Class<?> type, Set<Class<?>> types) {
if (type.isPrimitive() || Boolean.class.isAssignableFrom(type)
|| Number.class.isAssignableFrom(type) || Character.class.isAssignableFrom(type)
|| CharSequence.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type)
|| (type.isArray() && type.getComponentType().equals(Byte.TYPE))
|| Blob.class.isAssignableFrom(type) || Clob.class.isAssignableFrom(type)) {
return true;
}
if ((types != null) && !types.isEmpty()) {
for (Class<?> t : types) {
if ((t != null) && t.isAssignableFrom(type)) {
return true;
}
}
}
return false;
}
private static boolean ignoreAll(Field field) {
return ignore(field, null, Ignore.Type.ALL);
}
private static boolean enableSelect(Field field) {
return !ignore(field, Ignore.Type.SELECT, Ignore.Type.SELECT_INSERT,
Ignore.Type.SELECT_UPDATE);
}
private static boolean enableSelect(Ignore.Type type) {
return !exists(type, Ignore.Type.SELECT, Ignore.Type.SELECT_INSERT,
Ignore.Type.SELECT_UPDATE);
}
private static boolean enableInsert(Field field) {
return !ignore(field, Ignore.Type.SELECT_INSERT, Ignore.Type.INSERT,
Ignore.Type.INSERT_UPDATE);
}
private static boolean enableInsert(Ignore.Type type) {
return !exists(type, Ignore.Type.SELECT_INSERT, Ignore.Type.INSERT,
Ignore.Type.INSERT_UPDATE);
}
private static boolean enableUpdate(Field field) {
return !ignore(field, Ignore.Type.SELECT_UPDATE, Ignore.Type.INSERT_UPDATE,
Ignore.Type.UPDATE);
}
private static boolean enableUpdate(Ignore.Type type) {
return !exists(type, Ignore.Type.SELECT_UPDATE, Ignore.Type.INSERT_UPDATE,
Ignore.Type.UPDATE);
}
private static boolean ignore(Field field, Ignore.Type... types) {
Ignore ignore = field.getAnnotation(Ignore.class);
if (ignore == null) {
return false;
}
Ignore.Type type = ignore.value();
return exists(type, types);
}
private static boolean exists(Ignore.Type type, Ignore.Type... types) {
for (Ignore.Type t : types) {
if ((t == null) || (type == null)) {
if (type == t) {
return true;
}
} else {
if (type.equals(t)) {
return true;
}
}
}
return false;
}
private static boolean defaultIfNull(Field field) {
return field.getAnnotation(DefaultIfNull.class) != null;
}
private static String toSnake(String field, FieldToColumnCase ftc) {
if (ftc == null) {
return field;
}
String ret;
switch (ftc) {
case NONE:
ret = field;
break;
case SNAKE:
ret = toSnakeCase(field);
break;
case SNAKE_NUMBER:
ret = toSnakeCase(field, true);
break;
default:
throw new IllegalStateException(String.format("not found FieldColumnCase for %s",
ftc));
}
return ret;
}
public static boolean isNestedField(String field) {
if (field == null) {
return false;
}
return (field.indexOf(".") != -1);
}
public static String spritField(String field) {
if (StringUtils.isEmpty(field)) {
return field;
}
int index;
if ((index = field.lastIndexOf(".")) != -1) {
return field.substring(index + 1);
}
return field;
}
public static String joinTableColumn(String table, String column) {
if (StringUtils.isEmpty(table) || StringUtils.isEmpty(column)) {
return column;
}
return table + "." + column;
}
public static String joinPropertyField(String property, String field) {
if (StringUtils.isEmpty(property) || StringUtils.isEmpty(field)) {
return field;
}
return property + "." + field;
}
public static Object getPropertysValue(Object object, ColumnDefine columnDefine) {
if ((object == null) || (columnDefine == null)) {
return null;
}
Object obj = object;
try {
for (String property : columnDefine.getPropertys()) {
Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(property);
if (field == null) {
field = clazz.getField(property);
}
if (!Modifier.isPublic(field.getModifiers()) && !field.isAccessible()) {
field.setAccessible(true);
}
Object o = field.get(obj);
if (o == null) {
return null;
}
obj = o;
}
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException
| IllegalAccessException e) {
throw new RuntimeException(e);
}
return obj;
}
public static boolean verify(TableMapper tableMapper, Map<String, String> columnTypes,
TypeComparator typeComparator) {
Args.notNull(tableMapper, "tableMapper");
Args.notEmpty(columnTypes, "columnTypes");
if (tableMapper.isAlias()) {
throw new IllegalStateException("TableMapper must not be Alias.");
}
for (Entry<String, ColumnDefine> e : tableMapper.getFieldColumns(Flag.ALL).entrySet()) {
ColumnDefine cd = e.getValue();
Class<?> fieldType = cd.getType();
String columnName = cd.getName();
if (columnName == null) {
throw new IllegalStateException(String.format(
"not found column for field name %s in class %s.", e.getKey(),
fieldType.getName()));
}
String columnType = columnTypes.get(columnName);
if (columnType == null) {
throw new IllegalStateException(String.format(
"not found column type for column name %s, field name %s in class %s.",
columnName, e.getKey(), fieldType.getName()));
}
if ((typeComparator != null) && !typeComparator.compare(fieldType, columnType)) {
throw new IllegalStateException(String.format(
"illegal column type %s for column name %s, field name %s in class %s.",
columnType, columnName, e.getKey(), fieldType.getName()));
}
}
return true;
}
public static class FieldColumn implements Serializable {
private static final long serialVersionUID = 6744735469951734482L;
private final String field;
private final String column;
private boolean primary;
private boolean override;
private Ignore.Type ignore;
private boolean defaultIfNull;
protected FieldColumn(String field, String column, boolean primary, boolean override,
Ignore.Type ignore, boolean defaultIfNull) {
Args.notEmpty(field, "field");
this.field = field;
this.column = column;
this.primary = primary;
this.override = override;
this.ignore = ignore;
this.defaultIfNull = defaultIfNull;
}
public static FieldColumn create(String field) {
return create(field, null);
}
public static FieldColumn create(String field, String column) {
return create(field, column, false);
}
public static FieldColumn create(String field, String column, boolean primary) {
return create(field, column, primary, false);
}
public static FieldColumn create(String field, String column, boolean primary,
boolean override) {
return create(field, column, primary, override, null);
}
public static FieldColumn create(String field, String column, boolean primary,
boolean override, Ignore.Type ignore) {
return create(field, column, primary, override, ignore, false);
}
public static FieldColumn create(String field, String column, boolean primary,
boolean override, Ignore.Type ignore, boolean defaultIfNull) {
return new FieldColumn(field, column, primary, override, ignore, defaultIfNull);
}
public String field() {
return field;
}
public String column() {
return column;
}
public boolean primary() {
return primary;
}
public FieldColumn primaryTrue() {
return primary(true);
}
public FieldColumn primary(boolean primary) {
this.primary = primary;
return this;
}
public boolean override() {
return override;
}
public FieldColumn overrideTrue() {
return override(true);
}
public FieldColumn override(boolean override) {
this.override = override;
return this;
}
public Ignore.Type ignore() {
return ignore;
}
public FieldColumn ignore(Ignore.Type ignore) {
this.ignore = ignore;
return this;
}
public boolean defaultIfNull() {
return defaultIfNull;
}
public FieldColumn defaultIfNullTrue() {
return defaultIfNull(true);
}
public FieldColumn defaultIfNull(boolean defaultIfNull) {
this.defaultIfNull = defaultIfNull;
return this;
}
@Override
public int hashCode() {
if (HashCodeUtils.resolved()) {
return HashCodeUtils.hashCodeAlt(null, this);
}
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
if (EqualsUtils.resolved()) {
return EqualsUtils.equalsAlt(null, this, obj);
}
return super.equals(obj);
}
@Override
public String toString() {
if (ToStringUtils.resolved()) {
return ToStringUtils.toStringAlt(this);
}
return super.toString();
}
}
public static class PropertyReference implements Serializable {
private static final long serialVersionUID = 7439389826779040219L;
private final String property;
private final String reference;
protected PropertyReference(String property, String reference) {
Args.notEmpty(property, "property");
Args.notEmpty(reference, "reference");
this.property = property;
this.reference = reference;
}
public static PropertyReference create(String property, String reference) {
return new PropertyReference(property, reference);
}
public String property() {
return property;
}
public String reference() {
return reference;
}
@Override
public int hashCode() {
if (HashCodeUtils.resolved()) {
return HashCodeUtils.hashCodeAlt(null, this);
}
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
if (EqualsUtils.resolved()) {
return EqualsUtils.equalsAlt(null, this, obj);
}
return super.equals(obj);
}
@Override
public String toString() {
if (ToStringUtils.resolved()) {
return ToStringUtils.toStringAlt(this);
}
return super.toString();
}
}
}