package se.emilsjolander.sprinkles; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import se.emilsjolander.sprinkles.annotations.AutoIncrementPrimaryKey; import se.emilsjolander.sprinkles.annotations.Cacheable; import se.emilsjolander.sprinkles.annotations.CascadeDelete; import se.emilsjolander.sprinkles.annotations.Check; import se.emilsjolander.sprinkles.annotations.Column; import se.emilsjolander.sprinkles.annotations.ConflictClause; import se.emilsjolander.sprinkles.annotations.DynamicColumn; import se.emilsjolander.sprinkles.annotations.ForeignKey; import se.emilsjolander.sprinkles.annotations.NotNull; import se.emilsjolander.sprinkles.annotations.PrimaryKey; import se.emilsjolander.sprinkles.annotations.Unique; import se.emilsjolander.sprinkles.exceptions.AutoIncrementMustBeIntegerException; import se.emilsjolander.sprinkles.exceptions.CannotCascadeDeleteNonForeignKey; import se.emilsjolander.sprinkles.exceptions.DuplicateColumnException; import se.emilsjolander.sprinkles.exceptions.EmptyTableException; import se.emilsjolander.sprinkles.exceptions.NoPrimaryKeysException; import se.emilsjolander.sprinkles.typeserializers.SqlType; class ModelInfo { public static class ColumnField { String name; String sqlType; Field field; @Override public boolean equals(Object o) { if (o instanceof ColumnField) { return ((ColumnField) o).name.equals(name); } return false; } @Override public int hashCode() { return this.name.hashCode(); } } public static class StaticColumnField extends ColumnField { boolean isPrimaryKey; boolean isAutoIncrement; boolean isCascadeDelete; boolean isNotNull; boolean isForeignKey; String foreignKey; boolean isUnique; ConflictClause uniqueConflictClause; boolean hasCheck; String checkClause; } public static class DynamicColumnField extends ColumnField { } private static Map<Class<? extends Model>, ModelInfo> cache = new HashMap<Class<? extends Model>, ModelInfo>(); String tableName; Set<ColumnField> columns = new HashSet<ColumnField>(); List<DynamicColumnField> dynamicColumns = new ArrayList<DynamicColumnField>(); List<StaticColumnField> staticColumns = new ArrayList<StaticColumnField>(); List<StaticColumnField> foreignKeys = new ArrayList<StaticColumnField>(); List<StaticColumnField> primaryKeys = new ArrayList<StaticColumnField>(); StaticColumnField autoIncrementColumn; boolean cacheable = false; private ModelInfo() { // hide contructor } static ModelInfo from(Class<? extends Model> clazz) { if (cache.containsKey(clazz)) { return cache.get(clazz); } ModelInfo info = new ModelInfo(); final Field[] fields = Utils.getAllDeclaredFields(clazz, Object.class); for (Field field : fields) { if (field.isAnnotationPresent(DynamicColumn.class)) { DynamicColumnField column = new DynamicColumnField(); column.name = field.getAnnotation(DynamicColumn.class).value(); column.sqlType = Sprinkles.sInstance.getTypeSerializer(field.getType()).getSqlType().name(); column.field = field; info.dynamicColumns.add(column); if (!info.columns.add(column)) { throw new DuplicateColumnException(column.name); } } else if (field.isAnnotationPresent(Column.class)) { StaticColumnField column = new StaticColumnField(); column.name = field.getAnnotation(Column.class).value(); column.isAutoIncrement = field.isAnnotationPresent(AutoIncrementPrimaryKey.class); column.isForeignKey = field.isAnnotationPresent(ForeignKey.class); column.isPrimaryKey = field.isAnnotationPresent(PrimaryKey.class) || column.isAutoIncrement; column.isCascadeDelete = field.isAnnotationPresent(CascadeDelete.class); column.isUnique = field.isAnnotationPresent(Unique.class); column.isNotNull = field.isAnnotationPresent(NotNull.class); column.hasCheck = field.isAnnotationPresent(Check.class); if (column.isForeignKey) { column.foreignKey = field.getAnnotation(ForeignKey.class).value(); } else if (column.isCascadeDelete) { throw new CannotCascadeDeleteNonForeignKey(); } column.sqlType = Sprinkles.sInstance.getTypeSerializer(field.getType()).getSqlType().name(); column.field = field; if (column.isAutoIncrement && !column.sqlType.equals(SqlType.INTEGER.name())) { throw new AutoIncrementMustBeIntegerException(column.name); } if (column.isAutoIncrement && column.isForeignKey) { throw new IllegalStateException("A @AutoIncrementPrimaryKey field may not also be an @PrimaryKey or @ForeignKey field"); } if (column.isAutoIncrement) { info.autoIncrementColumn = column; } if (column.isForeignKey) { info.foreignKeys.add(column); } if (column.isPrimaryKey) { info.primaryKeys.add(column); } if (column.isUnique) { column.uniqueConflictClause = field.getAnnotation(Unique.class).value(); } if (column.hasCheck) { column.checkClause = field.getAnnotation(Check.class).value(); } info.staticColumns.add(column); if (!info.columns.add(column)) { throw new DuplicateColumnException(column.name); } } } if (info.columns.isEmpty()) { throw new EmptyTableException(clazz.getName()); } if (Model.class.isAssignableFrom(clazz)) { info.tableName = Utils.getTableName((Class<? extends Model>) clazz); if (clazz.isAnnotationPresent(Cacheable.class)) { info.cacheable = true; } try { clazz.getDeclaredConstructor(); clazz.newInstance(); } catch (Throwable e) { throw new RuntimeException(clazz.getSimpleName() + " error with default constructor : " + e.getClass().getSimpleName() + " : " + e.getMessage(), e); } if (info.primaryKeys.size() == 0) { throw new NoPrimaryKeysException(); } if (info.autoIncrementColumn != null && info.primaryKeys.size() > 1) { throw new IllegalStateException("A model with a field marked as @AutoIncrementPrimaryKey may not mark any other field with @PrimaryKey"); } } cache.put(clazz, info); return info; } }