View Javadoc
1   /*
2    * $Source$
3    * $Revision$
4    *
5    * Copyright (C) 2007 Tim Pizey
6    *
7    * Part of Melati (http://melati.org), a framework for the rapid
8    * development of clean, maintainable web applications.
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati (http://melati.org),
30   * if you would like to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     Tim Pizey <timp At paneris.org>
42   *     http://paneris.org/~timp
43   */
44  package org.melati.poem;
45  
46  import java.lang.reflect.Method;
47  import java.lang.reflect.Modifier;
48  import java.util.Collection;
49  import java.util.Enumeration;
50  import java.util.Hashtable;
51  import java.util.Map;
52  
53  import org.melati.poem.util.ClassUtils;
54  import org.melati.poem.util.StringUtils;
55  
56  
57  /**
58   * Given an Object or class create a set of Tables to represent the graph 
59   * it is the starting node of, and populate it for an Object.
60   * 
61   * The rules are a sub-set of the Hibernate rules: 
62   * Only public types can be persisted.
63   * Only members with a public setter and getter will be persisted.
64   * If the object has a field called Id then the Persistent troid column will be called 
65   * <tt>poemId</tt>.  
66   *  
67   * @author timp
68   * @since 12 Jun 2007
69   * 
70   */
71  public final class TableFactory {
72  
73    /**
74     * Disable instantiation.
75     */
76    private TableFactory() {
77    }
78  
79    /**
80     * @param db
81     *          the database to create table in and lookup existing tables
82     * @param pojo
83     *          class to introspect
84     * @return A new or existing table
85     */
86    public static Table<?> fromInstance(Database db, Object pojo) {
87      return fromClass(db, pojo.getClass());
88    }
89  
90    /**
91     * @param db
92     *          the database to create table in and lookup existing tables
93     * @param clazz
94     *          class to introspect
95     * @return A new or existing table
96     */
97    public static Table<?> fromClass(Database db, Class<?> clazz) {
98      if (clazz.isPrimitive())
99        throw new IllegalArgumentException(
100       "Cannot create a Table for a primitive type: " + clazz.getName());
101     if (clazz.isInterface())
102       throw new IllegalArgumentException(
103       "Cannot create a Table for an interface: " + clazz.getName());
104     if(!Modifier.isPublic(clazz.getModifiers())) 
105       throw new IllegalArgumentException(
106         "Cannot create a Table for a non public type: "+ clazz.getName());
107     if (clazz.isArray() && !(clazz == byte[].class))
108       throw new IllegalArgumentException("Cannot create a Table for an array: " + clazz.getName());
109     String name = clazz.getName();
110     String simpleName = name.substring(name.lastIndexOf('.') + 1);
111     try {
112       return db.getTable(simpleName);
113     } catch (NoSuchTablePoemException e) {
114       System.err.println("Creating a table for : " + name);
115     }
116     // We will have to create one
117     TableInfo tableInfo = (TableInfo)db.getTableInfoTable().newPersistent();
118     tableInfo.setName(simpleName);
119     tableInfo.setDisplayname(simpleName);
120     tableInfo.setDescription(simpleName + " introspected table");
121     tableInfo.setDisplayorder(13);
122     tableInfo.setSeqcached(Boolean.FALSE);
123     tableInfo.setCategory(db.getTableCategoryTable().NORMAL);
124     tableInfo.setCachelimit(555);
125     tableInfo.makePersistent();
126 
127     Table<Persistent> table = new JdbcTable<Persistent>(db, simpleName, DefinitionSource.runtime);
128     String troidName = "id";
129     if (ClassUtils.getNoArgMethod(clazz, "getId") != null &&
130         !(Persistent.class.isAssignableFrom(clazz)))
131       troidName = "poemId";
132     table.defineColumn(new ExtraColumn<Integer>(table, troidName, TroidPoemType.it,
133             DefinitionSource.runtime, table.getNextExtrasIndex()));
134     table.setTableInfo(tableInfo);
135     table.unifyWithColumnInfo();
136     table.unifyWithDB(null,troidName);
137 
138     PoemThread.commit();
139     db.defineTable(table);
140     Hashtable<String,Prop> props = new Hashtable<String,Prop>();
141 
142     Method[] methods = clazz.getMethods();
143 
144     for (int i = 0; i < methods.length; i++) {
145       if (Modifier.isPublic(methods[i].getModifiers())) {
146         if (methods[i].getName().startsWith("set") 
147             && ! methods[i].getName().equals("set")
148             && Character.isUpperCase(methods[i].getName().toCharArray()[3])
149             && methods[i].getParameterTypes().length == 1
150             && (methods[i].getParameterTypes()[0] == byte[].class || 
151                 ! methods[i].getParameterTypes()[0].isArray())
152             && !methods[i].getParameterTypes()[0].isInterface()
153             && ! Collection.class.isAssignableFrom(methods[i].getParameterTypes()[0])
154             && ! Map.class.isAssignableFrom(methods[i].getParameterTypes()[0])
155             ) {
156           String propName = methods[i].getName().substring(3);
157           propName = StringUtils.uncapitalised(propName);
158           Prop p = (Prop)props.get(propName);
159           Class<?> setClass = methods[i].getParameterTypes()[0];
160           if (p == null) {
161             p = new Prop(propName);
162             p.setSet(setClass);
163           } else { 
164             if (p.getGot() != null) { 
165               if(p.getGot() == setClass)
166                 p.setSet(setClass);
167             } else { 
168               p.setSet(setClass);                
169             }
170           }
171           props.put(propName, p);
172         }
173         
174         if (methods[i].getParameterTypes().length == 0
175             && ! methods[i].getReturnType().isInterface()
176             && (methods[i].getReturnType() == byte[].class || 
177                 !methods[i].getReturnType().isArray())
178             && !Collection.class.isAssignableFrom(methods[i].getReturnType())
179             && !Map.class.isAssignableFrom(methods[i].getReturnType())
180         ) {
181           String propName = null;
182           if ((methods[i].getName().startsWith("get")) && 
183                Character.isUpperCase(methods[i].getName().toCharArray()[3]))
184             propName = methods[i].getName().substring(3);
185           else if (methods[i].getName().startsWith("is") && 
186                    Character.isUpperCase(methods[i].getName().toCharArray()[2])) 
187             propName = methods[i].getName().substring(2);
188           if (propName != null) { 
189             propName = StringUtils.uncapitalised(propName);
190             Prop p = (Prop)props.get(propName);
191             Class<?> gotClass = methods[i].getReturnType();
192             if (p == null) {
193               p = new Prop(propName);
194               p.setGot(gotClass);
195             } else { 
196               p.setGot(gotClass);
197             }
198             props.put(propName, p);
199           }
200         }
201       }
202     }
203     Enumeration<Prop> propsEn = props.elements();
204     while (propsEn.hasMoreElements()) {
205       Prop p = (Prop)propsEn.nextElement();
206       if (p.getGot() != null && 
207           p.getGot() == p.getSet()) { 
208         addColumn(table, p.getName(), p.getGot(), p.getGot() == p.getSet());
209       }
210     }
211     return table;
212   }
213 
214   private static void addColumn(Table<?> table, String name, Class<?> fieldClass,
215           boolean hasSetter) {
216     ColumnInfo columnInfo = (ColumnInfo)table.getDatabase()
217             .getColumnInfoTable().newPersistent();
218     columnInfo.setTableinfo(table.getInfo());
219     columnInfo.setName(name);
220     
221     columnInfo.setDisplayname(name);
222     columnInfo.setDisplayorder(99);
223     columnInfo.setSearchability(Searchability.yes);
224     columnInfo.setIndexed(false);
225     columnInfo.setUnique(false);
226     columnInfo.setDescription("An introspected member");
227     columnInfo.setUsercreateable(hasSetter);
228     columnInfo.setUsereditable(hasSetter);
229     columnInfo.setSize(8);
230     columnInfo.setWidth(20);
231     columnInfo.setHeight(1);
232     columnInfo.setPrecision(0);
233     columnInfo.setScale(0);
234     columnInfo.setNullable(true);
235     columnInfo.setDisplaylevel(DisplayLevel.record);
236     if (fieldClass == java.lang.Boolean.class) {
237       columnInfo.setTypefactory(PoemTypeFactory.BOOLEAN);
238       columnInfo.setSize(1);
239       columnInfo.setWidth(10);
240     } else if (fieldClass == boolean.class) {
241       columnInfo.setTypefactory(PoemTypeFactory.BOOLEAN);
242       columnInfo.setSize(1);
243       columnInfo.setWidth(10);
244     } else if (fieldClass == java.lang.Integer.class) {
245       columnInfo.setTypefactory(PoemTypeFactory.INTEGER);
246     } else if (fieldClass == int.class) {
247       columnInfo.setTypefactory(PoemTypeFactory.INTEGER);
248     } else if (fieldClass == java.lang.Double.class) {
249       columnInfo.setTypefactory(PoemTypeFactory.DOUBLE);
250     } else if (fieldClass == double.class) {
251       columnInfo.setTypefactory(PoemTypeFactory.DOUBLE);
252     } else if (fieldClass == java.lang.Long.class) {
253       columnInfo.setTypefactory(PoemTypeFactory.LONG);
254     } else if (fieldClass == long.class) {
255       columnInfo.setTypefactory(PoemTypeFactory.LONG);
256     } else if (fieldClass == java.math.BigDecimal.class) {
257       columnInfo.setTypefactory(PoemTypeFactory.BIGDECIMAL);
258       columnInfo.setPrecision(22);
259       columnInfo.setScale(2);
260     } else if (fieldClass == java.lang.String.class) {
261       columnInfo.setTypefactory(PoemTypeFactory.STRING);
262       columnInfo.setSize(-1);
263     } else if (fieldClass == java.sql.Date.class) {
264       columnInfo.setTypefactory(PoemTypeFactory.DATE);
265     } else if (fieldClass == java.sql.Timestamp.class) {
266       columnInfo.setTypefactory(PoemTypeFactory.TIMESTAMP);
267     } else if (fieldClass == byte[].class) {
268       columnInfo.setTypefactory(PoemTypeFactory.BINARY);
269     }
270     else {
271       Table<?> referredTable = fromClass(table.getDatabase(), fieldClass);
272       columnInfo.setTypefactory(PoemTypeFactory.forCode(table.getDatabase(),
273               referredTable.getInfo().troid().intValue()));
274     }
275     columnInfo.makePersistent();
276     table.addColumnAndCommit(columnInfo);
277   }
278 
279   /**
280    * Details of a member variable.
281    */
282   static final class Prop {
283     String name = null;
284     Class<?> set = null;
285     Class<?> got = null;
286     
287     Prop(String name) {
288       this.name = name;
289     }
290 
291     /**
292      * @return the name
293      */
294     public String getName() {
295       return name;
296     }
297 
298     /**
299      * @return the got
300      */
301     public Class<?> getGot() {
302       return got;
303     }
304 
305     /**
306      * @param got
307      *          the Class of the getter
308      */
309     public void setGot(Class<?> got) {
310       this.got = got;
311     }
312 
313     /**
314      * @return the set
315      */
316     public Class<?> getSet() {
317       return set;
318     }
319 
320     /**
321      * @param set
322      *          the Class of the setter
323      */
324     public void setSet(Class<?> set) {
325       this.set = set;
326     }
327 
328   }
329 }