001: /* ====================================================================
002: * The LateralNZ Software License, Version 1.0
003: *
004: * Copyright (c) 2003 LateralNZ. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by
021: * LateralNZ (http://www.lateralnz.org/) and other third parties."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. The names "LateralNZ" must not be used to endorse or promote
026: * products derived from this software without prior written
027: * permission. For written permission, please
028: * contact oss@lateralnz.org.
029: *
030: * 5. Products derived from this software may not be called "Panther",
031: * or "Lateral" or "LateralNZ", nor may "PANTHER" or "LATERAL" or
032: * "LATERALNZ" appear in their name, without prior written
033: * permission of LateralNZ.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: *
049: * This software consists of voluntary contributions made by many
050: * individuals on behalf of LateralNZ. For more
051: * information on Lateral, please see http://www.lateralnz.com/ or
052: * http://www.lateralnz.org
053: *
054: */
055: package org.lateralnz.common.util;
056:
057: import java.io.File;
058: import java.io.FileWriter;
059: import java.io.FileInputStream;
060: import java.io.InputStreamReader;
061: import java.lang.reflect.Field;
062: import java.sql.ResultSet;
063: import java.sql.ResultSetMetaData;
064: import java.sql.Types;
065: import java.util.ArrayList;
066: import java.util.Date;
067: import java.util.HashMap;
068: import java.util.List;
069:
070: /**
071: * a singleton utility class which creates (& compiles) a class based upon a JDBC resultset
072: * and then returns a list with objects of that class populated with the resultset data
073: *
074: * @author J R Briggs
075: */
076: public class ResultSetConverter implements Constants {
077: private static final Class[] DATE_CLASS_TYPE = { Date.class };
078: private static final Class[] DOUBLE_CLASS_TYPE = { Double.class };
079: private static final Class[] LONG_CLASS_TYPE = { Long.class };
080: private static final Class[] STRING_CLASS_TYPE = { String.class };
081:
082: private static HashMap instances = new HashMap();
083:
084: private HashMap classes = new HashMap();
085: private String compilerExecutable;
086:
087: // Singleton instance.
088: private static ResultSetConverter instance = null;
089:
090: private ResultSetConverter(String compilerExecutable) {
091: setCompilerExecutable(compilerExecutable);
092: }
093:
094: private void setCompilerExecutable(String compilerExecutable) {
095: this .compilerExecutable = compilerExecutable;
096: }
097:
098: public static ResultSetConverter getInstance() throws Exception {
099: return getInstance("javac");
100: }
101:
102: /**
103: * Singleton method.
104: */
105: public static ResultSetConverter getInstance(
106: String compilerExecutable) throws Exception {
107: if (!instances.containsKey(compilerExecutable)) {
108: synchronized (instances) {
109: if (!instances.containsKey(compilerExecutable)) {
110: ResultSetConverter rsc = new ResultSetConverter(
111: compilerExecutable);
112: instances.put(compilerExecutable, rsc);
113: }
114: }
115: }
116:
117: return (ResultSetConverter) instances.get(compilerExecutable);
118: }
119:
120: private final MetaClass getClassForResultSet(String resultName,
121: ResultSet rs) throws Exception {
122: ResultSetMetaData md = rs.getMetaData();
123:
124: int count = md.getColumnCount();
125:
126: MetaClass metaclass = new MetaClass(count);
127:
128: String[] getmethods = new String[count];
129: String[] getomethods = new String[count];
130: String[] setmethods = new String[count];
131: String[] vars = new String[count];
132:
133: StringBuffer staticColnames = new StringBuffer(
134: "public static String[] colnames = new String[]{ ");
135:
136: for (int i = 0, j = 1; i < count; i++, j++) {
137: int type = md.getColumnType(j);
138: String colname = md.getColumnName(j);
139: staticColnames.append(QUOTE).append(colname).append(QUOTE);
140: if (i < count - 1) {
141: staticColnames.append(COMMA);
142: }
143: if (colname.indexOf(SPACE) >= 0) {
144: colname = StringUtils.replace(colname, SPACE,
145: UNDERSCORE);
146: }
147: colname = colname.substring(0, 1).toUpperCase()
148: + colname.substring(1);
149: switch (type) {
150: case Types.DECIMAL:
151: case Types.FLOAT:
152: case Types.DOUBLE:
153: metaclass.types[i] = MetaClass.DOUBLE;
154: vars[i] = "public double col" + j + ";";
155: getmethods[i] = "public double get" + colname
156: + "() { return this.col" + j + "; }";
157: getomethods[i] = " new Double(col" + j + ")";
158: setmethods[i] = "public void set" + colname
159: + "(double d) { this.col" + j + " = d; }";
160: break;
161: case Types.INTEGER:
162: case Types.SMALLINT:
163: case Types.TINYINT:
164: metaclass.types[i] = MetaClass.LONG;
165: vars[i] = "public long col" + j + ";";
166: getmethods[i] = "public long get" + colname
167: + "() { return this.col" + j + "; }";
168: getomethods[i] = " new Long(col" + j + ")";
169: setmethods[i] = "public void set" + colname
170: + "(long l) { this.col" + j + " = l; }";
171: break;
172: case Types.DATE:
173: case Types.TIME:
174: case Types.TIMESTAMP:
175: metaclass.types[i] = MetaClass.DATE;
176: vars[i] = "public Date col" + j + " = null;";
177: getmethods[i] = "public Date get" + colname
178: + "() { return this.col" + j + "; }";
179: getomethods[i] = " col" + j;
180: setmethods[i] = "public void set" + colname
181: + "(Date d) { this.col" + j + " = d; }";
182: break;
183: case Types.VARCHAR:
184: case Types.CHAR:
185: default:
186: metaclass.types[i] = MetaClass.STRING;
187: vars[i] = "public String col" + j + " = null;";
188: getmethods[i] = "public String get" + colname
189: + "() { return this.col" + j + "; }";
190: getomethods[i] = " col" + j;
191: setmethods[i] = "public void set" + colname
192: + "(String s) { this.col" + j + " = s; }";
193: break;
194: }
195: }
196:
197: staticColnames.append("};\n\n");
198: staticColnames
199: .append("public static String getColName(int idx) { return colnames[idx]; }\n\n");
200: staticColnames
201: .append("public static int getColCount() { return colnames.length; }\n\n");
202:
203: StringBuffer sb = new StringBuffer(
204: "import java.util.Date;\nimport java.util.Iterator;\nimport java.io.Serializable;\n\n");
205: sb.append("public class ResultSetValue_" + resultName
206: + " implements Iterator, Serializable {\n");
207: sb.append(staticColnames.toString()).append(NEWLINE).append(
208: NEWLINE);
209: sb.append("int pos = 0;\nint max = ").append(count).append(
210: ";\n\n");
211:
212: for (int i = 0; i < count; i++) {
213: sb.append(vars[i]).append(NEWLINE);
214: }
215: for (int i = 0; i < count; i++) {
216: sb.append(getmethods[i]).append(NEWLINE);
217: sb.append(setmethods[i]).append(NEWLINE);
218: }
219: sb
220: .append("public boolean hasNext() { return (pos < max); }\n\n");
221: sb.append("public Object next() {\ntry {\nswitch (pos) {\n");
222: for (int i = 0; i < count; i++) {
223: sb.append("case ").append(i).append(": return ").append(
224: getomethods[i]).append(";\n");
225: }
226: sb
227: .append("default: return null;\n}\n} finally { pos++; }\n}\npublic void remove() { }\n");
228:
229: if (count > 0) {
230: sb.append("\npublic String toString() {\nreturn \"\" + ");
231: for (int i = 1; i <= count; i++) {
232: sb.append("col").append(i);
233: if (i < count) {
234: sb.append(" + \",\" + ");
235: }
236: }
237: sb.append(";\n}\n");
238: }
239:
240: sb.append(NEWLINE).append("}");
241:
242: FileWriter fw = new FileWriter("ResultSetValue_" + resultName
243: + ".java");
244: fw.write(sb.toString());
245:
246: fw.close();
247:
248: Process p = Runtime.getRuntime().exec(
249: compilerExecutable + " ResultSetValue_" + resultName
250: + ".java");
251: p.waitFor();
252: if (p.exitValue() != 0) {
253: InputStreamReader isr = null;
254: try {
255: isr = new InputStreamReader(p.getErrorStream());
256: throw new Exception("error compiling "
257: + StringUtils.readFrom(isr));
258: } finally {
259: IOUtils.close(isr);
260: }
261: }
262:
263: File f = new File("ResultSetValue_" + resultName + ".class");
264: FileInputStream fis = null;
265: byte[] b;
266: try {
267: fis = new FileInputStream(f);
268: b = new byte[(int) f.length()];
269: fis.read(b);
270: } finally {
271: IOUtils.close(fis);
272: }
273:
274: InternalClassLoader icl = new InternalClassLoader();
275: Class c = icl.getClass("ResultSetValue_" + resultName, b);
276: metaclass.c = c;
277: classes.put(resultName, metaclass);
278:
279: return metaclass;
280: }
281:
282: private final Field[] getFields(MetaClass mc) throws Exception {
283: Field[] f = new Field[mc.numOfFields];
284: for (int i = 0, j = 1; i < f.length; i++, j++) {
285: f[i] = mc.c.getField("col" + j);
286: }
287: return f;
288: }
289:
290: /**
291: * return a resultset as a list of objects representing the rows data of the resultset
292: * @param resultSetName the name to refer to resultsets of this type (so we don't have
293: * to create the class every time we call this method
294: * @param rs the resultset data
295: * @param forceClassReload forces the class to be rebuilt
296: */
297: public List getResultSetAsList(String resultSetName, ResultSet rs,
298: boolean forceClassReload) throws Exception {
299: MetaClass metaclass;
300: if (!classes.containsKey(resultSetName) || forceClassReload) {
301: metaclass = getClassForResultSet(resultSetName, rs);
302: } else {
303: metaclass = (MetaClass) classes.get(resultSetName);
304: }
305:
306: Field[] fields = getFields(metaclass);
307: Object[] val = new Object[1];
308: ArrayList al = new ArrayList();
309: while (rs.next()) {
310: Object valobj = metaclass.c.newInstance();
311:
312: for (int i = 0, j = 1; i < fields.length; i++, j++) {
313: switch (metaclass.types[i]) {
314: case MetaClass.DATE:
315: fields[i].set(valobj, rs.getTimestamp(j));
316: break;
317: case MetaClass.DOUBLE:
318: fields[i].setDouble(valobj, rs.getDouble(j));
319: break;
320: case MetaClass.LONG:
321: fields[i].setLong(valobj, rs.getLong(j));
322: break;
323: default:
324: fields[i].set(valobj, rs.getString(j));
325: break;
326: }
327: }
328: al.add(valobj);
329: }
330:
331: return al;
332: }
333:
334: public void reset(String name) {
335: classes.remove(name);
336: }
337:
338: /**
339: * the classloader used to load the 'converter' classes
340: */
341: class InternalClassLoader extends ClassLoader {
342: public InternalClassLoader() {
343: super ();
344: }
345:
346: public Class getClass(String name, byte[] b) {
347: return super .defineClass(name, b, 0, b.length);
348: }
349: }
350:
351: class MetaClass {
352: public static final int STRING = 0;
353: public static final int LONG = 1;
354: public static final int DOUBLE = 2;
355: public static final int DATE = 3;
356:
357: int numOfFields;
358: int[] types;
359: Class c;
360:
361: public MetaClass(int numOfFields) {
362: this .numOfFields = numOfFields;
363: types = new int[numOfFields];
364: }
365: }
366: }
|