TableFactory.java
/*
* $Source$
* $Revision$
*
* Copyright (C) 2007 Tim Pizey
*
* Part of Melati (http://melati.org), a framework for the rapid
* development of clean, maintainable web applications.
*
* Melati is free software; Permission is granted to copy, distribute
* and/or modify this software under the terms either:
*
* a) the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version,
*
* or
*
* b) any version of the Melati Software License, as published
* at http://melati.org
*
* You should have received a copy of the GNU General Public License and
* the Melati Software License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
* GNU General Public License and visit http://melati.org to obtain the
* Melati Software License.
*
* Feel free to contact the Developers of Melati (http://melati.org),
* if you would like to work out a different arrangement than the options
* outlined here. It is our intention to allow Melati to be used by as
* wide an audience as possible.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Contact details for copyright holder:
*
* Tim Pizey <timp At paneris.org>
* http://paneris.org/~timp
*/
package org.melati.poem;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import org.melati.poem.util.ClassUtils;
import org.melati.poem.util.StringUtils;
/**
* Given an Object or class create a set of Tables to represent the graph
* it is the starting node of, and populate it for an Object.
*
* The rules are a sub-set of the Hibernate rules:
* Only public types can be persisted.
* Only members with a public setter and getter will be persisted.
* If the object has a field called Id then the Persistent troid column will be called
* <tt>poemId</tt>.
*
* @author timp
* @since 12 Jun 2007
*
*/
public final class TableFactory {
/**
* Disable instantiation.
*/
private TableFactory() {
}
/**
* @param db
* the database to create table in and lookup existing tables
* @param pojo
* class to introspect
* @return A new or existing table
*/
public static Table<?> fromInstance(Database db, Object pojo) {
return fromClass(db, pojo.getClass());
}
/**
* @param db
* the database to create table in and lookup existing tables
* @param clazz
* class to introspect
* @return A new or existing table
*/
public static Table<?> fromClass(Database db, Class<?> clazz) {
if (clazz.isPrimitive())
throw new IllegalArgumentException(
"Cannot create a Table for a primitive type: " + clazz.getName());
if (clazz.isInterface())
throw new IllegalArgumentException(
"Cannot create a Table for an interface: " + clazz.getName());
if(!Modifier.isPublic(clazz.getModifiers()))
throw new IllegalArgumentException(
"Cannot create a Table for a non public type: "+ clazz.getName());
if (clazz.isArray() && !(clazz == byte[].class))
throw new IllegalArgumentException("Cannot create a Table for an array: " + clazz.getName());
String name = clazz.getName();
String simpleName = name.substring(name.lastIndexOf('.') + 1);
try {
return db.getTable(simpleName);
} catch (NoSuchTablePoemException e) {
System.err.println("Creating a table for : " + name);
}
// We will have to create one
TableInfo tableInfo = (TableInfo)db.getTableInfoTable().newPersistent();
tableInfo.setName(simpleName);
tableInfo.setDisplayname(simpleName);
tableInfo.setDescription(simpleName + " introspected table");
tableInfo.setDisplayorder(13);
tableInfo.setSeqcached(Boolean.FALSE);
tableInfo.setCategory(db.getTableCategoryTable().NORMAL);
tableInfo.setCachelimit(555);
tableInfo.makePersistent();
Table<Persistent> table = new JdbcTable<Persistent>(db, simpleName, DefinitionSource.runtime);
String troidName = "id";
if (ClassUtils.getNoArgMethod(clazz, "getId") != null &&
!(Persistent.class.isAssignableFrom(clazz)))
troidName = "poemId";
table.defineColumn(new ExtraColumn<Integer>(table, troidName, TroidPoemType.it,
DefinitionSource.runtime, table.getNextExtrasIndex()));
table.setTableInfo(tableInfo);
table.unifyWithColumnInfo();
table.unifyWithDB(null,troidName);
PoemThread.commit();
db.defineTable(table);
Hashtable<String,Prop> props = new Hashtable<String,Prop>();
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
if (Modifier.isPublic(methods[i].getModifiers())) {
if (methods[i].getName().startsWith("set")
&& ! methods[i].getName().equals("set")
&& Character.isUpperCase(methods[i].getName().toCharArray()[3])
&& methods[i].getParameterTypes().length == 1
&& (methods[i].getParameterTypes()[0] == byte[].class ||
! methods[i].getParameterTypes()[0].isArray())
&& !methods[i].getParameterTypes()[0].isInterface()
&& ! Collection.class.isAssignableFrom(methods[i].getParameterTypes()[0])
&& ! Map.class.isAssignableFrom(methods[i].getParameterTypes()[0])
) {
String propName = methods[i].getName().substring(3);
propName = StringUtils.uncapitalised(propName);
Prop p = (Prop)props.get(propName);
Class<?> setClass = methods[i].getParameterTypes()[0];
if (p == null) {
p = new Prop(propName);
p.setSet(setClass);
} else {
if (p.getGot() != null) {
if(p.getGot() == setClass)
p.setSet(setClass);
} else {
p.setSet(setClass);
}
}
props.put(propName, p);
}
if (methods[i].getParameterTypes().length == 0
&& ! methods[i].getReturnType().isInterface()
&& (methods[i].getReturnType() == byte[].class ||
!methods[i].getReturnType().isArray())
&& !Collection.class.isAssignableFrom(methods[i].getReturnType())
&& !Map.class.isAssignableFrom(methods[i].getReturnType())
) {
String propName = null;
if ((methods[i].getName().startsWith("get")) &&
Character.isUpperCase(methods[i].getName().toCharArray()[3]))
propName = methods[i].getName().substring(3);
else if (methods[i].getName().startsWith("is") &&
Character.isUpperCase(methods[i].getName().toCharArray()[2]))
propName = methods[i].getName().substring(2);
if (propName != null) {
propName = StringUtils.uncapitalised(propName);
Prop p = (Prop)props.get(propName);
Class<?> gotClass = methods[i].getReturnType();
if (p == null) {
p = new Prop(propName);
p.setGot(gotClass);
} else {
p.setGot(gotClass);
}
props.put(propName, p);
}
}
}
}
Enumeration<Prop> propsEn = props.elements();
while (propsEn.hasMoreElements()) {
Prop p = (Prop)propsEn.nextElement();
if (p.getGot() != null &&
p.getGot() == p.getSet()) {
addColumn(table, p.getName(), p.getGot(), p.getGot() == p.getSet());
}
}
return table;
}
private static void addColumn(Table<?> table, String name, Class<?> fieldClass,
boolean hasSetter) {
ColumnInfo columnInfo = (ColumnInfo)table.getDatabase()
.getColumnInfoTable().newPersistent();
columnInfo.setTableinfo(table.getInfo());
columnInfo.setName(name);
columnInfo.setDisplayname(name);
columnInfo.setDisplayorder(99);
columnInfo.setSearchability(Searchability.yes);
columnInfo.setIndexed(false);
columnInfo.setUnique(false);
columnInfo.setDescription("An introspected member");
columnInfo.setUsercreateable(hasSetter);
columnInfo.setUsereditable(hasSetter);
columnInfo.setSize(8);
columnInfo.setWidth(20);
columnInfo.setHeight(1);
columnInfo.setPrecision(0);
columnInfo.setScale(0);
columnInfo.setNullable(true);
columnInfo.setDisplaylevel(DisplayLevel.record);
if (fieldClass == java.lang.Boolean.class) {
columnInfo.setTypefactory(PoemTypeFactory.BOOLEAN);
columnInfo.setSize(1);
columnInfo.setWidth(10);
} else if (fieldClass == boolean.class) {
columnInfo.setTypefactory(PoemTypeFactory.BOOLEAN);
columnInfo.setSize(1);
columnInfo.setWidth(10);
} else if (fieldClass == java.lang.Integer.class) {
columnInfo.setTypefactory(PoemTypeFactory.INTEGER);
} else if (fieldClass == int.class) {
columnInfo.setTypefactory(PoemTypeFactory.INTEGER);
} else if (fieldClass == java.lang.Double.class) {
columnInfo.setTypefactory(PoemTypeFactory.DOUBLE);
} else if (fieldClass == double.class) {
columnInfo.setTypefactory(PoemTypeFactory.DOUBLE);
} else if (fieldClass == java.lang.Long.class) {
columnInfo.setTypefactory(PoemTypeFactory.LONG);
} else if (fieldClass == long.class) {
columnInfo.setTypefactory(PoemTypeFactory.LONG);
} else if (fieldClass == java.math.BigDecimal.class) {
columnInfo.setTypefactory(PoemTypeFactory.BIGDECIMAL);
columnInfo.setPrecision(22);
columnInfo.setScale(2);
} else if (fieldClass == java.lang.String.class) {
columnInfo.setTypefactory(PoemTypeFactory.STRING);
columnInfo.setSize(-1);
} else if (fieldClass == java.sql.Date.class) {
columnInfo.setTypefactory(PoemTypeFactory.DATE);
} else if (fieldClass == java.sql.Timestamp.class) {
columnInfo.setTypefactory(PoemTypeFactory.TIMESTAMP);
} else if (fieldClass == byte[].class) {
columnInfo.setTypefactory(PoemTypeFactory.BINARY);
}
else {
Table<?> referredTable = fromClass(table.getDatabase(), fieldClass);
columnInfo.setTypefactory(PoemTypeFactory.forCode(table.getDatabase(),
referredTable.getInfo().troid().intValue()));
}
columnInfo.makePersistent();
table.addColumnAndCommit(columnInfo);
}
/**
* Details of a member variable.
*/
static final class Prop {
String name = null;
Class<?> set = null;
Class<?> got = null;
Prop(String name) {
this.name = name;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @return the got
*/
public Class<?> getGot() {
return got;
}
/**
* @param got
* the Class of the getter
*/
public void setGot(Class<?> got) {
this.got = got;
}
/**
* @return the set
*/
public Class<?> getSet() {
return set;
}
/**
* @param set
* the Class of the setter
*/
public void setSet(Class<?> set) {
this.set = set;
}
}
}