package siena.jdbc.ddl;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ddlutils.model.Column;
import org.apache.ddlutils.model.Database;
import org.apache.ddlutils.model.IndexColumn;
import org.apache.ddlutils.model.NonUniqueIndex;
import org.apache.ddlutils.model.Table;
import org.apache.ddlutils.model.UniqueIndex;
import siena.ClassInfo;
import siena.DateTime;
import siena.Generator;
import siena.Id;
import siena.Index;
import siena.Json;
import siena.Max;
import siena.NotNull;
import siena.SienaRestrictedApiException;
import siena.SimpleDate;
import siena.Text;
import siena.Time;
import siena.Unique;
import siena.core.DecimalPrecision;
import siena.core.Polymorphic;
import siena.embed.Embedded;
public class DdlGenerator {
public String DB = "mysql";
private Map<String, Table> tables = new HashMap<String, Table>();
private Database database = new Database();
public DdlGenerator(){
}
public DdlGenerator(String db){
this.DB = db;
}
public Table getTable(String name) {
return tables.get(name);
}
public Database getDatabase() {
return database;
}
public Table addTable(Class<?> clazz) {
if(Modifier.isAbstract(clazz.getModifiers())){
return null;
}
Table table = new Table();
ClassInfo info = ClassInfo.getClassInfo(clazz);
table.setName(info.tableName);
table.setType("MyISAM");
database.addTable(table);
Map<String, UniqueIndex> uniques = new HashMap<String, UniqueIndex>();
Map<String, NonUniqueIndex> indexes = new HashMap<String, NonUniqueIndex>();
/* columns */
for (Field field : info.allFields) {
String[] columns = ClassInfo.getColumnNames(field);
boolean notNull = field.getAnnotation(NotNull.class) != null;
Class<?> type = field.getType();
if(!ClassInfo.isModel(type) || (ClassInfo.isModel(type) && ClassInfo.isEmbedded(field))) {
Column column = createColumn(clazz, field, columns[0]);
if(notNull || type.isPrimitive()) {
column.setRequired(true);
if(type.isPrimitive() && !ClassInfo.isId(field)) { // TODO: add also Boolean, Long, Double,... ?
if(type == Boolean.TYPE) {
column.setDefaultValue("false");
} else {
column.setDefaultValue("0");
}
}
}
Id id = field.getAnnotation(Id.class);
if(id != null) {
column.setPrimaryKey(true);
column.setRequired(true);
// auto_increments managed ONLY for long
if(id.value() == Generator.AUTO_INCREMENT
&& (Long.TYPE == type || Long.class.isAssignableFrom(type)))
column.setAutoIncrement(true);
// adds index on primary key
/*UniqueIndex i = uniques.get(columns[0]);
if(i == null) {
i = new UniqueIndex();
i.setName(columns[0]);
uniques.put(columns[0], i);
table.addIndex(i);
}
fillIndex(i, field);*/
}
table.addColumn(column);
} else {
List<Field> keys = ClassInfo.getClassInfo(type).keys;
for (int i = 0; i < columns.length; i++) {
Field f = keys.get(i);
Column column = createColumn(clazz, f, columns[i]);
if(notNull)
column.setRequired(true);
table.addColumn(column);
}
}
}
/* indexes */
for (Field field : info.updateFields) {
Index index = field.getAnnotation(Index.class);
if(index != null) {
String[] names = index.value();
for (String name : names) {
NonUniqueIndex i = indexes.get(name);
if(i == null) {
i = new NonUniqueIndex();
i.setName(name);
indexes.put(name, i);
table.addIndex(i);
}
fillIndex(i, field);
}
}
Unique unique = field.getAnnotation(Unique.class);
if(unique != null) {
String[] names = unique.value();
for (String name : names) {
UniqueIndex i = uniques.get(name);
if(i == null) {
i = new UniqueIndex();
i.setName(name);
uniques.put(name, i);
table.addIndex(i);
}
fillIndex(i, field);
}
}
}
tables.put(table.getName(), table);
return table;
}
private void fillIndex(org.apache.ddlutils.model.Index i, Field field) {
String[] columns = ClassInfo.getColumnNames(field);
for (String string : columns) {
IndexColumn ic = new IndexColumn(string);
i.addColumn(ic);
}
}
private Column createColumn(Class<?> clazz, Field field, String col) {
Class<?> type = field.getType();
Column column = new Column();
column.setName(col);
int columnType;
if(type == Byte.class || type == Byte.TYPE) columnType = Types.TINYINT;
else if(type == Short.class || type == Short.TYPE) columnType = Types.SMALLINT;
else if(type == Integer.class || type == Integer.TYPE) columnType = Types.INTEGER;
else if(type == Long.class || type == Long.TYPE) columnType = Types.BIGINT;
else if(type == Float.class || type == Float.TYPE) columnType = Types.FLOAT; // TODO verify
else if(type == Double.class || type == Double.TYPE) columnType = Types.DOUBLE; // TODO verify
else if(type == String.class) {
if(field.getAnnotation(Text.class) != null) {
columnType = Types.LONGVARCHAR;
} else {
columnType = Types.VARCHAR;
Max max = field.getAnnotation(Max.class);
if(max == null){
//throw new SienaRestrictedApiException(DB, "createColumn", "Field "+field.getName()+" in class "
// +clazz.getName()+" doesn't have a @Max annotation");
// default is 255 chars as in hibernate
column.setSize("255");
}
else column.setSize(""+max.value());
}
}
else if(type == Boolean.class || type == Boolean.TYPE) columnType = Types.BOOLEAN;
else if(type == Date.class) {
if(field.getAnnotation(DateTime.class) != null)
columnType = Types.TIMESTAMP;
else if(field.getAnnotation(Time.class) != null)
columnType = Types.TIME;
else if(field.getAnnotation(SimpleDate.class) != null)
columnType = Types.DATE;
else
columnType = Types.TIMESTAMP;
} else if(type == Json.class) {
columnType = Types.LONGVARCHAR;
} else if(type == byte[].class){
columnType = Types.BLOB;
} else if(Enum.class.isAssignableFrom(type)){
// enums are stored as string
columnType = Types.VARCHAR;
Max max = field.getAnnotation(Max.class);
if(max == null)
column.setSize(""+255); // fixes by default to this value in order to prevent alter tables every time
else column.setSize(""+max.value());
} else if(type == BigDecimal.class){
DecimalPrecision an = field.getAnnotation(DecimalPrecision.class);
if(an == null) {
columnType = Types.DECIMAL;
column.setSizeAndScale(19, 2);
}
else {
if(an.storageType() == DecimalPrecision.StorageType.NATIVE){
columnType = Types.DECIMAL;
column.setSizeAndScale(an.size(), an.scale());
}else if(an.storageType() == DecimalPrecision.StorageType.STRING) {
columnType = Types.VARCHAR;
// should be an.size+"."+sign
column.setSize((an.size()+2)+"");
}else if(an.storageType() == DecimalPrecision.StorageType.DOUBLE) {
columnType = Types.DOUBLE;
}else {
columnType = Types.DECIMAL;
column.setSizeAndScale(19, 2);
}
}
}
else {
Embedded embedded = field.getAnnotation(Embedded.class);
if(embedded != null) {
if("h2".equals(DB)){
columnType = Types.CLOB;
}
else {
columnType = Types.LONGVARCHAR;
}
} else if(field.isAnnotationPresent(Polymorphic.class)){
columnType = Types.BLOB;
}else {
throw new SienaRestrictedApiException(DB, "createColumn", "Unsupported type for field "
+clazz.getName()+"."+field.getName());
}
}
column.setTypeCode(columnType);
return column;
}
}