001: /*
002: $Header: /cvsroot/xorm/xorm/tools/src/org/xorm/tools/generator/GenerateInterfaces.java,v 1.4 2003/06/23 17:39:43 dcheckoway Exp $
003:
004: This file is part of XORM.
005:
006: XORM is free software; you can redistribute it and/or modify
007: it under the terms of the GNU General Public License as published by
008: the Free Software Foundation; either version 2 of the License, or
009: (at your option) any later version.
010:
011: XORM is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU General Public License for more details.
015:
016: You should have received a copy of the GNU General Public License
017: along with XORM; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package org.xorm.tools.generator;
021:
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.FileOutputStream;
025: import java.io.PrintStream;
026: import java.io.PrintWriter;
027: import java.io.StringWriter;
028: import java.math.BigInteger;
029: import java.sql.*;
030: import java.util.ArrayList;
031: import java.util.Date;
032: import java.util.Hashtable;
033: import java.util.Iterator;
034: import java.util.Properties;
035: import javax.sql.DataSource;
036: import org.xorm.XORM;
037: import org.xorm.datastore.sql.SQLConnectionInfo;
038: import org.xorm.datastore.sql.SQLType;
039:
040: /**
041: * Connects to the database and writes out interfaces for all of the
042: * tables and columns it finds. Requires a recent JDBC driver. To execute,
043: * give the properties file as the first parameter.
044: */
045: public class GenerateInterfaces {
046: /** SQL to Java type mappings for non-nullable columns. Note that since
047: the column is not nullable, we can use primitive types in many cases.
048: */
049: private static Hashtable nonStringTypes;
050: static {
051: nonStringTypes = new Hashtable();
052: nonStringTypes.put("bigint", BigInteger.class);
053: nonStringTypes.put("bit", Boolean.TYPE);
054: nonStringTypes.put("boolean", Boolean.TYPE);
055: //let char be String...nonStringTypes.put("char", Character.TYPE);
056: nonStringTypes.put("date", Date.class);
057: nonStringTypes.put("decimal", Double.TYPE);
058: nonStringTypes.put("double", Double.TYPE);
059: nonStringTypes.put("float", Float.TYPE);
060: nonStringTypes.put("integer", Integer.TYPE);
061: nonStringTypes.put("numeric", Double.TYPE);
062: nonStringTypes.put("real", Double.TYPE);
063: nonStringTypes.put("smallint", Integer.TYPE);
064: nonStringTypes.put("time", Date.class);
065: nonStringTypes.put("timestamp", Date.class);
066: nonStringTypes.put("tinyint", Integer.TYPE);
067: }
068:
069: /** SQL to Java type mappings for nullable columns. Note that none of
070: these uses the .TYPE primitive class. If the table column is an
071: integer, but it's nullable, then we need to return Integer, not int.
072: That way the field value can be treated as null properly.
073: */
074: private static Hashtable nullableNonStringTypes;
075: static {
076: nullableNonStringTypes = new Hashtable();
077: nullableNonStringTypes.put("bigint", BigInteger.class);
078: nullableNonStringTypes.put("bit", Boolean.class);
079: nullableNonStringTypes.put("boolean", Boolean.class);
080: //let char be String...nullableNonStringTypes.put("char", Character.class);
081: nullableNonStringTypes.put("date", Date.class);
082: nullableNonStringTypes.put("decimal", Double.class);
083: nullableNonStringTypes.put("double", Double.class);
084: nullableNonStringTypes.put("float", Float.class);
085: nullableNonStringTypes.put("integer", Integer.class);
086: nullableNonStringTypes.put("numeric", Double.class);
087: nullableNonStringTypes.put("real", Double.class);
088: nullableNonStringTypes.put("smallint", Integer.class);
089: nullableNonStringTypes.put("time", Date.class);
090: nullableNonStringTypes.put("timestamp", Date.class);
091: nullableNonStringTypes.put("tinyint", Integer.class);
092: }
093:
094: public static void main(String[] argv) {
095: int exitCode = 0;
096: try {
097: String propertiesFileName = null;
098: String outputDirName = null;
099: String packageName = null;
100: boolean abstractClass = false;
101: String tablePattern = null;
102:
103: for (int k = 0; k < argv.length; ++k) {
104: if (argv[k].startsWith("-prop")) {
105: propertiesFileName = argv[++k];
106: } else if (argv[k].startsWith("-out")) {
107: outputDirName = argv[++k];
108: } else if (argv[k].startsWith("-pack")) {
109: packageName = argv[++k];
110: } else if (argv[k].startsWith("-ab")) {
111: abstractClass = new Boolean(argv[++k])
112: .booleanValue();
113: } else if (argv[k].startsWith("-t")) {
114: tablePattern = argv[++k];
115: } else {
116: System.out
117: .println("Unrecognized command line option: "
118: + argv[k]);
119: }
120: }
121:
122: if (propertiesFileName == null
123: || propertiesFileName.equals("")) {
124: System.out
125: .println("Usage: GenerateInterfaces -properties path/to/xorm.properties [-output outputDir] [-package packageName]");
126: System.exit(0);
127: }
128:
129: if (outputDirName == null || outputDirName.equals("")) {
130: outputDirName = "./";
131: }
132:
133: File outputDir = new File(outputDirName);
134: if (!outputDir.exists()) {
135: outputDir.mkdir();
136: }
137: System.out.println("Output is going to: "
138: + outputDir.getCanonicalPath());
139:
140: Properties properties = new Properties();
141: properties.load(new FileInputStream(propertiesFileName));
142: SQLConnectionInfo info = new SQLConnectionInfo();
143: info.setProperties(properties);
144: DataSource dataSource = (DataSource) info.getDataSource();
145: Connection connection = dataSource.getConnection();
146: DatabaseMetaData metadata = connection.getMetaData();
147: ResultSet results;
148:
149: String[] tableTypes = new String[1];
150:
151: // Read list of tables
152: tableTypes[0] = "TABLE";
153: results = metadata.getTables(null, null, null, tableTypes);
154: ArrayList tables = new ArrayList();
155: while (results.next()) {
156: String tableName = results.getString("TABLE_NAME");
157: if (tablePattern != null && !tablePattern.equals("")
158: && !tableName.matches(tablePattern)) {
159: continue;
160: }
161: tables.add(results.getString("TABLE_NAME"));
162: }
163: results.close();
164:
165: // Read columns for each table
166: Iterator i = tables.iterator();
167: while (i.hasNext()) {
168: String tableName = (String) i.next();
169:
170: // See if it has a primary key
171: results = metadata
172: .getPrimaryKeys(null, null, tableName);
173: ArrayList primaryKeyColumnNames = new ArrayList();
174: while (results.next()) {
175: String pkColumnName = results
176: .getString("COLUMN_NAME");
177: System.out.println("Primary key column: "
178: + pkColumnName);
179: primaryKeyColumnNames.add(pkColumnName);
180: }
181: results.close();
182: if (primaryKeyColumnNames.isEmpty()) {
183: System.out
184: .println("Skipping table with no primary key: "
185: + tableName);
186: continue;
187: }
188:
189: System.out.println("Processing table: " + tableName);
190:
191: StringWriter strWriter = new StringWriter();
192: PrintWriter methodsOut = new PrintWriter(strWriter);
193:
194: ArrayList importedClasses = new ArrayList();
195: boolean firstColumn = true;
196:
197: results = metadata.getColumns(null, null, tableName,
198: null);
199: while (results.next()) {
200: String columnName = results
201: .getString("COLUMN_NAME");
202: if (primaryKeyColumnNames.contains(columnName)) {
203: // Skip any primary key columns...this may be slightly
204: // flawed in cases where a table has more than one
205: // primary key column. But I think 99% of the time
206: // people use a numeric id column. We'll see how this
207: // ends up working out (assuming anybody uses this).
208: System.out
209: .println("Skipping primary key column: "
210: + columnName);
211: continue;
212: }
213:
214: String returnType = null;
215: if (columnName.toLowerCase().endsWith("_id")) {
216: String refTableName = columnName.substring(0,
217: columnName.length() - 3);
218: // See if the table exists
219: ResultSet refResults = metadata.getTables(null,
220: null, refTableName, tableTypes);
221: if (refResults != null && refResults.next()) {
222: // Yep, this id column most likely references the
223: // other table...at least it's named that way.
224: System.out.println("Column \"" + columnName
225: + "\" appears to reference table "
226: + refTableName);
227: returnType = makeJavaName(refTableName);
228: columnName = refTableName;
229: }
230: }
231:
232: if (returnType == null) {
233: String typeName = SQLType.nameFor(results
234: .getInt("DATA_TYPE"));
235: Class javaType;
236: if (results.getInt("NULLABLE") == metadata.columnNoNulls) {
237: // The column is not nullable...so use the
238: // non-nullable set of type mappings.
239: javaType = (Class) nonStringTypes
240: .get(typeName.toLowerCase());
241: } else {
242: // The column may be nullable...so use the nullable
243: // set of type mappings.
244: javaType = (Class) nullableNonStringTypes
245: .get(typeName.toLowerCase());
246: }
247: if (javaType == null) {
248: javaType = String.class;
249: } else if (javaType.equals(Date.class)
250: || javaType.equals(BigInteger.class)) {
251: if (!importedClasses.contains(javaType)) {
252: importedClasses.add(javaType);
253: }
254: }
255: returnType = makeSimpleClassName(javaType);
256: }
257:
258: if (firstColumn) {
259: firstColumn = false;
260: } else {
261: methodsOut.println();
262: }
263:
264: //System.out.println("Column: " + columnName + ", type: " + typeName + ", return: " + returnType);
265: methodsOut.print(" ");
266: if (abstractClass) {
267: methodsOut.print("public abstract ");
268: }
269: methodsOut.println(returnType + " get"
270: + makeJavaName(columnName) + "();");
271: methodsOut.print(" ");
272: if (abstractClass) {
273: methodsOut.print("public abstract ");
274: }
275: methodsOut.println("void set"
276: + makeJavaName(columnName) + "("
277: + returnType + " val);");
278: }
279: results.close();
280:
281: String className = makeJavaName(tableName);
282: File outputFile = new File(outputDir, className
283: + ".java");
284: System.out.println("Writing file: "
285: + outputFile.getCanonicalPath());
286: FileOutputStream fileOut = new FileOutputStream(
287: outputFile);
288: PrintStream out = new PrintStream(fileOut);
289: if (packageName != null && !packageName.equals("")) {
290: out.println("package " + packageName + ";");
291: out.println();
292: }
293: if (!importedClasses.isEmpty()) {
294: for (Iterator iter = importedClasses.iterator(); iter
295: .hasNext();) {
296: Class clazz = (Class) iter.next();
297: out.println("import " + clazz.getName() + ";");
298: }
299: out.println();
300: }
301: out.print("public ");
302: if (abstractClass) {
303: out.print("abstract class");
304: } else {
305: out.print("interface");
306: }
307: out.println(" " + className + " {");
308: out.print(strWriter.toString()); // no newline afterward
309: out.println("}");
310: }
311: connection.close();
312: } catch (Throwable t) {
313: t.printStackTrace();
314: exitCode = 1;
315: } finally {
316: Runtime.getRuntime().exit(exitCode);
317: }
318: }
319:
320: /** Make a Java coding style name out of a database column name.
321: This simply makes the first character uppercase, and will uppercase
322: any character following an underscore.
323: @param columnName the database column name
324: @return the Java coding style name
325: */
326: public static String makeJavaName(String columnName) {
327: StringBuffer buf = new StringBuffer();
328: boolean nextUpper = false;
329: for (int k = 0; k < columnName.length(); ++k) {
330: if (k == 0) {
331: buf.append(Character.toUpperCase(columnName.charAt(k)));
332: } else if (columnName.charAt(k) == '_') {
333: nextUpper = true;
334: } else {
335: if (nextUpper) {
336: buf.append(Character.toUpperCase(columnName
337: .charAt(k)));
338: nextUpper = false;
339: } else {
340: buf.append(columnName.charAt(k));
341: }
342: }
343: }
344: return buf.toString();
345: }
346:
347: /** Get the "simple" class name, which has the package removed
348: @param clazz the class
349: @return the simple class name string
350: */
351: public static String makeSimpleClassName(Class clazz) {
352: if (clazz.getPackage() == null) {
353: return clazz.getName();
354: }
355:
356: String packageName = clazz.getPackage().getName();
357: String className = clazz.getName();
358:
359: String shortName = className;
360:
361: if (packageName != null) {
362: int packageLength = packageName.length();
363: int packageIdx = className.indexOf(packageName);
364: shortName = className.substring(packageIdx + packageLength
365: + 1); //chop off the package name, and the last "."
366: } else if (shortName.startsWith("L")) {
367: shortName = shortName.substring(1);
368: }
369:
370: if (shortName.endsWith(";")) {
371: //chop off the trailing semicolon
372: shortName = shortName.substring(0, shortName.length() - 1);
373: }
374:
375: return shortName;
376: }
377: }
|