KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > dbutils > BasicRowProcessor


1 /*
2  * $Header: /home/cvs/jakarta-commons/dbutils/src/java/org/apache/commons/dbutils/BasicRowProcessor.java,v 1.5 2003/11/11 00:53:19 dgraham Exp $
3  * $Revision: 1.5 $
4  * $Date: 2003/11/11 00:53:19 $
5  *
6  * ====================================================================
7  *
8  * The Apache Software License, Version 1.1
9  *
10  * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
11  * reserved.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  *
17  * 1. Redistributions of source code must retain the above copyright
18  * notice, this list of conditions and the following disclaimer.
19  *
20  * 2. Redistributions in binary form must reproduce the above copyright
21  * notice, this list of conditions and the following disclaimer in
22  * the documentation and/or other materials provided with the
23  * distribution.
24  *
25  * 3. The end-user documentation included with the redistribution, if
26  * any, must include the following acknowledgement:
27  * "This product includes software developed by the
28  * Apache Software Foundation (http://www.apache.org/)."
29  * Alternately, this acknowledgement may appear in the software itself,
30  * if and wherever such third-party acknowledgements normally appear.
31  *
32  * 4. The names "The Jakarta Project", "Commons", and "Apache Software
33  * Foundation" must not be used to endorse or promote products derived
34  * from this software without prior written permission. For written
35  * permission, please contact apache@apache.org.
36  *
37  * 5. Products derived from this software may not be called "Apache"
38  * nor may "Apache" appear in their names without prior written
39  * permission of the Apache Software Foundation.
40  *
41  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44  * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52  * SUCH DAMAGE.
53  * ====================================================================
54  *
55  * This software consists of voluntary contributions made by many
56  * individuals on behalf of the Apache Software Foundation. For more
57  * information on the Apache Software Foundation, please see
58  * <http://www.apache.org/>.
59  *
60  */

61
62 package org.apache.commons.dbutils;
63
64 import java.beans.BeanInfo JavaDoc;
65 import java.beans.IntrospectionException JavaDoc;
66 import java.beans.Introspector JavaDoc;
67 import java.beans.PropertyDescriptor JavaDoc;
68 import java.lang.reflect.InvocationTargetException JavaDoc;
69 import java.lang.reflect.Method JavaDoc;
70 import java.sql.ResultSet JavaDoc;
71 import java.sql.ResultSetMetaData JavaDoc;
72 import java.sql.SQLException JavaDoc;
73 import java.util.ArrayList JavaDoc;
74 import java.util.HashMap JavaDoc;
75 import java.util.Iterator JavaDoc;
76 import java.util.List JavaDoc;
77 import java.util.Map JavaDoc;
78
79 /**
80  * Basic implementation of the <code>RowProcessor</code> interface.
81  * This class is a thread-safe Singleton.
82  *
83  * @see RowProcessor
84  *
85  * @author Henri Yandell
86  * @author Juozas Baliuka
87  * @author David Graham
88  * @author Yoav Shapira
89  */

90 public class BasicRowProcessor implements RowProcessor {
91
92     /**
93      * Set a bean's primitive properties to these defaults when SQL NULL
94      * is returned. These are the same as the defaults that ResultSet get*
95      * methods return in the event of a NULL column.
96      */

97     private static final Map JavaDoc primitiveDefaults = new HashMap JavaDoc();
98
99     static {
100         primitiveDefaults.put(Integer.TYPE, new Integer JavaDoc(0));
101         primitiveDefaults.put(Short.TYPE, new Short JavaDoc((short) 0));
102         primitiveDefaults.put(Byte.TYPE, new Byte JavaDoc((byte) 0));
103         primitiveDefaults.put(Float.TYPE, new Float JavaDoc(0));
104         primitiveDefaults.put(Double.TYPE, new Double JavaDoc(0));
105         primitiveDefaults.put(Long.TYPE, new Long JavaDoc(0));
106         primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
107         primitiveDefaults.put(Character.TYPE, new Character JavaDoc('\u0000'));
108     }
109
110     /**
111      * Special array index that indicates there is no bean property that
112      * matches a column from a ResultSet.
113      */

114     private static final int PROPERTY_NOT_FOUND = -1;
115
116     /**
117      * The Singleton instance of this class.
118      */

119     private static final BasicRowProcessor instance = new BasicRowProcessor();
120
121     /**
122      * Returns the Singleton instance of this class.
123      *
124      * @return The single instance of this class.
125      */

126     public static BasicRowProcessor instance() {
127         return instance;
128     }
129
130     /**
131      * Protected constructor for BasicRowProcessor subclasses only.
132      */

133     protected BasicRowProcessor() {
134         super();
135     }
136
137     /**
138      * Convert a <code>ResultSet</code> row into an <code>Object[]</code>.
139      * This implementation copies column values into the array in the same
140      * order they're returned from the <code>ResultSet</code>. Array elements
141      * will be set to <code>null</code> if the column was SQL NULL.
142      *
143      * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)
144      */

145     public Object JavaDoc[] toArray(ResultSet JavaDoc rs) throws SQLException JavaDoc {
146         ResultSetMetaData JavaDoc meta = rs.getMetaData();
147         int cols = meta.getColumnCount();
148         Object JavaDoc[] result = new Object JavaDoc[cols];
149
150         for (int i = 0; i < cols; i++) {
151             result[i] = rs.getObject(i + 1);
152         }
153
154         return result;
155     }
156
157     /**
158      * Convert a <code>ResultSet</code> row into a JavaBean. This
159      * implementation uses reflection and <code>BeanInfo</code> classes to
160      * match column names to bean property names. Properties are matched to
161      * columns based on several factors:
162      * <br/>
163      * <ol>
164      * <li>
165      * The class has a writable property with the same name as a column.
166      * The name comparison is case insensitive.
167      * </li>
168      *
169      * <li>
170      * The property's set method parameter type matches the column
171      * type. If the data types do not match, the setter will not be called.
172      * </li>
173      * </ol>
174      *
175      * <p>
176      * Primitive bean properties are set to their defaults when SQL NULL is
177      * returned from the <code>ResultSet</code>. Numeric fields are set to 0
178      * and booleans are set to false. Object bean properties are set to
179      * <code>null</code> when SQL NULL is returned. This is the same behavior
180      * as the <code>ResultSet</code> get* methods.
181      * </p>
182      *
183      * @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet, java.lang.Class)
184      */

185     public Object JavaDoc toBean(ResultSet JavaDoc rs, Class JavaDoc type) throws SQLException JavaDoc {
186
187         PropertyDescriptor JavaDoc[] props = this.propertyDescriptors(type);
188
189         ResultSetMetaData JavaDoc rsmd = rs.getMetaData();
190
191         int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
192
193         int cols = rsmd.getColumnCount();
194
195         return this.createBean(rs, type, props, columnToProperty, cols);
196     }
197
198     /**
199      * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans.
200      * This implementation uses reflection and <code>BeanInfo</code> classes to
201      * match column names to bean property names. Properties are matched to
202      * columns based on several factors:
203      * <br/>
204      * <ol>
205      * <li>
206      * The class has a writable property with the same name as a column.
207      * The name comparison is case insensitive.
208      * </li>
209      *
210      * <li>
211      * The property's set method parameter type matches the column
212      * type. If the data types do not match, the setter will not be called.
213      * </li>
214      * </ol>
215      *
216      * <p>
217      * Primitive bean properties are set to their defaults when SQL NULL is
218      * returned from the <code>ResultSet</code>. Numeric fields are set to 0
219      * and booleans are set to false. Object bean properties are set to
220      * <code>null</code> when SQL NULL is returned. This is the same behavior
221      * as the <code>ResultSet</code> get* methods.
222      * </p>
223      *
224      * @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet, java.lang.Class)
225      */

226     public List JavaDoc toBeanList(ResultSet JavaDoc rs, Class JavaDoc type) throws SQLException JavaDoc {
227         List JavaDoc results = new ArrayList JavaDoc();
228
229         if (!rs.next()) {
230             return results;
231         }
232
233         PropertyDescriptor JavaDoc[] props = this.propertyDescriptors(type);
234         ResultSetMetaData JavaDoc rsmd = rs.getMetaData();
235         int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
236         int cols = rsmd.getColumnCount();
237
238         do {
239             results.add(this.createBean(rs, type, props, columnToProperty, cols));
240
241         } while (rs.next());
242
243         return results;
244     }
245
246     /**
247      * Creates a new object and initializes its fields from the ResultSet.
248      *
249      * @param rs The result set
250      * @param type The bean type (the return type of the object)
251      * @param props The property descriptors
252      * @param columnToProperty The column indices in the result set
253      * @param cols The number of columns
254      * @return An initialized object.
255      * @throws SQLException If a database error occurs
256      */

257     private Object JavaDoc createBean(
258         ResultSet JavaDoc rs,
259         Class JavaDoc type,
260         PropertyDescriptor JavaDoc[] props,
261         int[] columnToProperty,
262         int cols)
263         throws SQLException JavaDoc {
264
265         Object JavaDoc bean = this.newInstance(type);
266
267         for (int i = 1; i <= cols; i++) {
268
269             if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
270                 continue;
271             }
272             
273             Object JavaDoc value = rs.getObject(i);
274
275             PropertyDescriptor JavaDoc prop = props[columnToProperty[i]];
276             Class JavaDoc propType = prop.getPropertyType();
277
278             if (propType != null && value == null && propType.isPrimitive()) {
279                 value = primitiveDefaults.get(propType);
280             }
281
282             this.callSetter(bean, prop, value);
283         }
284
285         return bean;
286     }
287
288     /**
289      * The positions in the returned array represent column numbers. The values
290      * stored at each position represent the index in the PropertyDescriptor[]
291      * for the bean property that matches the column name. If no bean property
292      * was found for a column, the position is set to PROPERTY_NOT_FOUND.
293      *
294      * @param rsmd The result set meta data containing column information
295      * @param props The bean property descriptors
296      * @return An int[] with column index to property index mappings. The 0th
297      * element is meaningless as column indexing starts at 1.
298      *
299      * @throws SQLException If a database error occurs
300      */

301     private int[] mapColumnsToProperties(
302         ResultSetMetaData JavaDoc rsmd,
303         PropertyDescriptor JavaDoc[] props)
304         throws SQLException JavaDoc {
305
306         int cols = rsmd.getColumnCount();
307         int columnToProperty[] = new int[cols + 1];
308
309         for (int col = 1; col <= cols; col++) {
310             String JavaDoc columnName = rsmd.getColumnName(col);
311             for (int i = 0; i < props.length; i++) {
312
313                 if (columnName.equalsIgnoreCase(props[i].getName())) {
314                     columnToProperty[col] = i;
315                     break;
316
317                 } else {
318                     columnToProperty[col] = PROPERTY_NOT_FOUND;
319                 }
320             }
321         }
322
323         return columnToProperty;
324     }
325
326     /**
327      * Convert a <code>ResultSet</code> row into a <code>Map</code>. This
328      * implementation returns a <code>Map</code> with case insensitive column
329      * names as keys. Calls to <code>map.get("COL")</code> and
330      * <code>map.get("col")</code> return the same value.
331      * @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet)
332      */

333     public Map JavaDoc toMap(ResultSet JavaDoc rs) throws SQLException JavaDoc {
334         Map JavaDoc result = new CaseInsensitiveHashMap();
335         ResultSetMetaData JavaDoc rsmd = rs.getMetaData();
336         int cols = rsmd.getColumnCount();
337
338         for (int i = 1; i <= cols; i++) {
339             result.put(rsmd.getColumnName(i), rs.getObject(i));
340         }
341
342         return result;
343     }
344
345     /**
346      * Calls the setter method on the target object for the given property.
347      * If no setter method exists for the property, this method does nothing.
348      * @param target The object to set the property on.
349      * @param prop The property to set.
350      * @param value The value to pass into the setter.
351      * @throws SQLException if an error occurs setting the property.
352      */

353     private void callSetter(
354         Object JavaDoc target,
355         PropertyDescriptor JavaDoc prop,
356         Object JavaDoc value)
357         throws SQLException JavaDoc {
358
359         Method JavaDoc setter = prop.getWriteMethod();
360
361         if (setter == null) {
362             return;
363         }
364
365         Class JavaDoc[] params = setter.getParameterTypes();
366         try {
367             // Don't call setter if the value object isn't the right type
368
if (this.isCompatibleType(value, params[0])) {
369                 setter.invoke(target, new Object JavaDoc[] { value });
370             }
371
372         } catch (IllegalArgumentException JavaDoc e) {
373             throw new SQLException JavaDoc(
374                 "Cannot set " + prop.getName() + ": " + e.getMessage());
375
376         } catch (IllegalAccessException JavaDoc e) {
377             throw new SQLException JavaDoc(
378                 "Cannot set " + prop.getName() + ": " + e.getMessage());
379
380         } catch (InvocationTargetException JavaDoc e) {
381             throw new SQLException JavaDoc(
382                 "Cannot set " + prop.getName() + ": " + e.getMessage());
383         }
384     }
385
386     /**
387      * ResultSet.getObject() returns an Integer object for an INT column. The
388      * setter method for the property might take an Integer or a primitive int.
389      * This method returns true if the value can be successfully passed into
390      * the setter method. Remember, Method.invoke() handles the unwrapping
391      * of Integer into an int.
392      *
393      * @param value The value to be passed into the setter method.
394      * @param type The setter's parameter type.
395      * @return boolean True if the value is compatible.
396      */

397     private boolean isCompatibleType(Object JavaDoc value, Class JavaDoc type) {
398         // Do object check first, then primitives
399
if (value == null || type.isInstance(value)) {
400             return true;
401
402         } else if (
403             type.equals(Integer.TYPE) && Integer JavaDoc.class.isInstance(value)) {
404             return true;
405
406         } else if (type.equals(Long.TYPE) && Long JavaDoc.class.isInstance(value)) {
407             return true;
408
409         } else if (
410             type.equals(Double.TYPE) && Double JavaDoc.class.isInstance(value)) {
411             return true;
412
413         } else if (type.equals(Float.TYPE) && Float JavaDoc.class.isInstance(value)) {
414             return true;
415
416         } else if (type.equals(Short.TYPE) && Short JavaDoc.class.isInstance(value)) {
417             return true;
418
419         } else if (type.equals(Byte.TYPE) && Byte JavaDoc.class.isInstance(value)) {
420             return true;
421
422         } else if (
423             type.equals(Character.TYPE) && Character JavaDoc.class.isInstance(value)) {
424             return true;
425
426         } else if (
427             type.equals(Boolean.TYPE) && Boolean JavaDoc.class.isInstance(value)) {
428             return true;
429
430         } else {
431             return false;
432         }
433
434     }
435
436     /**
437      * Returns a new instance of the given Class.
438      *
439      * @param c The Class to create an object from.
440      * @return A newly created object of the Class.
441      * @throws SQLException if creation failed.
442      */

443     private Object JavaDoc newInstance(Class JavaDoc c) throws SQLException JavaDoc {
444         try {
445             return c.newInstance();
446
447         } catch (InstantiationException JavaDoc e) {
448             throw new SQLException JavaDoc(
449                 "Cannot create " + c.getName() + ": " + e.getMessage());
450
451         } catch (IllegalAccessException JavaDoc e) {
452             throw new SQLException JavaDoc(
453                 "Cannot create " + c.getName() + ": " + e.getMessage());
454         }
455     }
456
457     /**
458      * Returns a PropertyDescriptor[] for the given Class.
459      *
460      * @param c The Class to retrieve PropertyDescriptors for.
461      * @return A PropertyDescriptor[] describing the Class.
462      * @throws SQLException if introspection failed.
463      */

464     private PropertyDescriptor JavaDoc[] propertyDescriptors(Class JavaDoc c)
465         throws SQLException JavaDoc {
466         // Introspector caches BeanInfo classes for better performance
467
BeanInfo JavaDoc beanInfo = null;
468         try {
469             beanInfo = Introspector.getBeanInfo(c);
470
471         } catch (IntrospectionException JavaDoc e) {
472             throw new SQLException JavaDoc(
473                 "Bean introspection failed: " + e.getMessage());
474         }
475
476         return beanInfo.getPropertyDescriptors();
477     }
478
479     /**
480      * A Map that converts all keys to lowercase Strings for case insensitive
481      * lookups. This is needed for the toMap() implementation because
482      * databases don't consistenly handle the casing of column names.
483      */

484     private static class CaseInsensitiveHashMap extends HashMap JavaDoc {
485
486         /**
487          * @see java.util.Map#containsKey(java.lang.Object)
488          */

489         public boolean containsKey(Object JavaDoc key) {
490             return super.containsKey(key.toString().toLowerCase());
491         }
492
493         /**
494          * @see java.util.Map#get(java.lang.Object)
495          */

496         public Object JavaDoc get(Object JavaDoc key) {
497             return super.get(key.toString().toLowerCase());
498         }
499
500         /**
501          * @see java.util.Map#put(java.lang.Object, java.lang.Object)
502          */

503         public Object JavaDoc put(Object JavaDoc key, Object JavaDoc value) {
504             return super.put(key.toString().toLowerCase(), value);
505         }
506
507         /**
508          * @see java.util.Map#putAll(java.util.Map)
509          */

510         public void putAll(Map JavaDoc m) {
511             Iterator JavaDoc iter = m.keySet().iterator();
512             while (iter.hasNext()) {
513                 Object JavaDoc key = iter.next();
514                 Object JavaDoc value = m.get(key);
515                 this.put(key, value);
516             }
517         }
518
519         /**
520          * @see java.util.Map#remove(java.lang.ObjecT)
521          */

522         public Object JavaDoc remove(Object JavaDoc key) {
523             return super.remove(key.toString().toLowerCase());
524         }
525     }
526     
527 }
528
Popular Tags