Coverage Report - org.apache.commons.dbutils.BeanProcessor
 
Classes in this File Line Coverage Branch Coverage Complexity
BeanProcessor
0%
0/121
0%
0/140
9.8
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.dbutils;
 18  
 
 19  
 import java.beans.BeanInfo;
 20  
 import java.beans.IntrospectionException;
 21  
 import java.beans.Introspector;
 22  
 import java.beans.PropertyDescriptor;
 23  
 import java.lang.reflect.InvocationTargetException;
 24  
 import java.lang.reflect.Method;
 25  
 import java.sql.ResultSet;
 26  
 import java.sql.ResultSetMetaData;
 27  
 import java.sql.SQLException;
 28  
 import java.sql.Timestamp;
 29  
 import java.util.ArrayList;
 30  
 import java.util.Arrays;
 31  
 import java.util.HashMap;
 32  
 import java.util.List;
 33  
 import java.util.Map;
 34  
 
 35  
 /**
 36  
  * <p>
 37  
  * <code>BeanProcessor</code> matches column names to bean property names 
 38  
  * and converts <code>ResultSet</code> columns into objects for those bean 
 39  
  * properties.  Subclasses should override the methods in the processing chain
 40  
  * to customize behavior.
 41  
  * </p>
 42  
  * 
 43  
  * <p>
 44  
  * This class is thread-safe.
 45  
  * </p>
 46  
  * 
 47  
  * @see BasicRowProcessor
 48  
  * 
 49  
  * @since DbUtils 1.1
 50  
  */
 51  
 public class BeanProcessor {
 52  
 
 53  
     /**
 54  
      * Special array value used by <code>mapColumnsToProperties</code> that 
 55  
      * indicates there is no bean property that matches a column from a 
 56  
      * <code>ResultSet</code>.
 57  
      */
 58  
     protected static final int PROPERTY_NOT_FOUND = -1;
 59  
 
 60  
     /**
 61  
      * Set a bean's primitive properties to these defaults when SQL NULL 
 62  
      * is returned.  These are the same as the defaults that ResultSet get* 
 63  
      * methods return in the event of a NULL column.
 64  
      */
 65  0
     private static final Map primitiveDefaults = new HashMap();
 66  
 
 67  
     static {
 68  0
         primitiveDefaults.put(Integer.TYPE, new Integer(0));
 69  0
         primitiveDefaults.put(Short.TYPE, new Short((short) 0));
 70  0
         primitiveDefaults.put(Byte.TYPE, new Byte((byte) 0));
 71  0
         primitiveDefaults.put(Float.TYPE, new Float(0));
 72  0
         primitiveDefaults.put(Double.TYPE, new Double(0));
 73  0
         primitiveDefaults.put(Long.TYPE, new Long(0));
 74  0
         primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
 75  0
         primitiveDefaults.put(Character.TYPE, new Character('\u0000'));
 76  0
     }
 77  
 
 78  
     /**
 79  
      * Constructor for BeanProcessor.
 80  
      */
 81  
     public BeanProcessor() {
 82  0
         super();
 83  0
     }
 84  
 
 85  
     /**
 86  
      * Convert a <code>ResultSet</code> row into a JavaBean.  This 
 87  
      * implementation uses reflection and <code>BeanInfo</code> classes to 
 88  
      * match column names to bean property names.  Properties are matched to 
 89  
      * columns based on several factors:
 90  
      * <br/>
 91  
      * <ol>
 92  
      *     <li>
 93  
      *     The class has a writable property with the same name as a column.
 94  
      *     The name comparison is case insensitive.
 95  
      *     </li>
 96  
      * 
 97  
      *     <li>
 98  
      *     The column type can be converted to the property's set method 
 99  
      *     parameter type with a ResultSet.get* method.  If the conversion fails
 100  
      *     (ie. the property was an int and the column was a Timestamp) an
 101  
      *     SQLException is thrown.
 102  
      *     </li>
 103  
      * </ol>
 104  
      * 
 105  
      * <p>
 106  
      * Primitive bean properties are set to their defaults when SQL NULL is
 107  
      * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
 108  
      * and booleans are set to false.  Object bean properties are set to 
 109  
      * <code>null</code> when SQL NULL is returned.  This is the same behavior
 110  
      * as the <code>ResultSet</code> get* methods.
 111  
      * </p>
 112  
      *
 113  
      * @param rs ResultSet that supplies the bean data
 114  
      * @param type Class from which to create the bean instance
 115  
      * @throws SQLException if a database access error occurs
 116  
      * @return the newly created bean
 117  
      */
 118  
     public Object toBean(ResultSet rs, Class type) throws SQLException {
 119  
 
 120  0
         PropertyDescriptor[] props = this.propertyDescriptors(type);
 121  
 
 122  0
         ResultSetMetaData rsmd = rs.getMetaData();
 123  0
         int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
 124  
 
 125  0
         return this.createBean(rs, type, props, columnToProperty);
 126  
     }
 127  
 
 128  
     /**
 129  
      * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans.  
 130  
      * This implementation uses reflection and <code>BeanInfo</code> classes to 
 131  
      * match column names to bean property names. Properties are matched to 
 132  
      * columns based on several factors:
 133  
      * <br/>
 134  
      * <ol>
 135  
      *     <li>
 136  
      *     The class has a writable property with the same name as a column.
 137  
      *     The name comparison is case insensitive.
 138  
      *     </li>
 139  
      * 
 140  
      *     <li>
 141  
      *     The column type can be converted to the property's set method 
 142  
      *     parameter type with a ResultSet.get* method.  If the conversion fails
 143  
      *     (ie. the property was an int and the column was a Timestamp) an
 144  
      *     SQLException is thrown.
 145  
      *     </li>
 146  
      * </ol>
 147  
      * 
 148  
      * <p>
 149  
      * Primitive bean properties are set to their defaults when SQL NULL is
 150  
      * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
 151  
      * and booleans are set to false.  Object bean properties are set to 
 152  
      * <code>null</code> when SQL NULL is returned.  This is the same behavior
 153  
      * as the <code>ResultSet</code> get* methods.
 154  
      * </p>
 155  
      *
 156  
      * @param rs ResultSet that supplies the bean data
 157  
      * @param type Class from which to create the bean instance
 158  
      * @throws SQLException if a database access error occurs
 159  
      * @return the newly created List of beans
 160  
      */
 161  
     public List toBeanList(ResultSet rs, Class type) throws SQLException {
 162  0
         List results = new ArrayList();
 163  
 
 164  0
         if (!rs.next()) {
 165  0
             return results;
 166  
         }
 167  
 
 168  0
         PropertyDescriptor[] props = this.propertyDescriptors(type);
 169  0
         ResultSetMetaData rsmd = rs.getMetaData();
 170  0
         int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
 171  
 
 172  
         do {
 173  0
             results.add(this.createBean(rs, type, props, columnToProperty));
 174  0
         } while (rs.next());
 175  
 
 176  0
         return results;
 177  
     }
 178  
 
 179  
     /**
 180  
      * Creates a new object and initializes its fields from the ResultSet.
 181  
      *
 182  
      * @param rs The result set.
 183  
      * @param type The bean type (the return type of the object).
 184  
      * @param props The property descriptors.
 185  
      * @param columnToProperty The column indices in the result set.
 186  
      * @return An initialized object.
 187  
      * @throws SQLException if a database error occurs.
 188  
      */
 189  
     private Object createBean(ResultSet rs, Class type,
 190  
             PropertyDescriptor[] props, int[] columnToProperty)
 191  
             throws SQLException {
 192  
 
 193  0
         Object bean = this.newInstance(type);
 194  
 
 195  0
         for (int i = 1; i < columnToProperty.length; i++) {
 196  
 
 197  0
             if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
 198  0
                 continue;
 199  
             }
 200  
 
 201  0
             PropertyDescriptor prop = props[columnToProperty[i]];
 202  0
             Class propType = prop.getPropertyType();
 203  
 
 204  0
             Object value = this.processColumn(rs, i, propType);
 205  
 
 206  0
             if (propType != null && value == null && propType.isPrimitive()) {
 207  0
                 value = primitiveDefaults.get(propType);
 208  
             }
 209  
 
 210  0
             this.callSetter(bean, prop, value);
 211  
         }
 212  
 
 213  0
         return bean;
 214  
     }
 215  
 
 216  
     /**
 217  
      * Calls the setter method on the target object for the given property.
 218  
      * If no setter method exists for the property, this method does nothing.
 219  
      * @param target The object to set the property on.
 220  
      * @param prop The property to set.
 221  
      * @param value The value to pass into the setter.
 222  
      * @throws SQLException if an error occurs setting the property.
 223  
      */
 224  
     private void callSetter(Object target, PropertyDescriptor prop, Object value)
 225  
             throws SQLException {
 226  
 
 227  0
         Method setter = prop.getWriteMethod();
 228  
 
 229  0
         if (setter == null) {
 230  0
             return;
 231  
         }
 232  
 
 233  0
         Class[] params = setter.getParameterTypes();
 234  
         try {
 235  
             // convert types for some popular ones
 236  0
             if (value != null) {
 237  0
                 if (value instanceof java.util.Date) {
 238  0
                     if (params[0].getName().equals("java.sql.Date")) {
 239  0
                         value = new java.sql.Date(((java.util.Date) value).getTime());
 240  
                     } else
 241  0
                     if (params[0].getName().equals("java.sql.Time")) {
 242  0
                         value = new java.sql.Time(((java.util.Date) value).getTime());
 243  
                     } else
 244  0
                     if (params[0].getName().equals("java.sql.Timestamp")) {
 245  0
                         value = new java.sql.Timestamp(((java.util.Date) value).getTime());
 246  
                     }
 247  
                 }
 248  
             }
 249  
 
 250  
             // Don't call setter if the value object isn't the right type 
 251  0
             if (this.isCompatibleType(value, params[0])) {
 252  0
                 setter.invoke(target, new Object[] { value });
 253  
             } else {
 254  0
               throw new SQLException(
 255  
                   "Cannot set " + prop.getName() + ": incompatible types.");
 256  
             }
 257  
 
 258  0
         } catch (IllegalArgumentException e) {
 259  0
             throw new SQLException(
 260  
                 "Cannot set " + prop.getName() + ": " + e.getMessage());
 261  
 
 262  0
         } catch (IllegalAccessException e) {
 263  0
             throw new SQLException(
 264  
                 "Cannot set " + prop.getName() + ": " + e.getMessage());
 265  
 
 266  0
         } catch (InvocationTargetException e) {
 267  0
             throw new SQLException(
 268  
                 "Cannot set " + prop.getName() + ": " + e.getMessage());
 269  0
         }
 270  0
     }
 271  
 
 272  
     /**
 273  
      * ResultSet.getObject() returns an Integer object for an INT column.  The
 274  
      * setter method for the property might take an Integer or a primitive int.
 275  
      * This method returns true if the value can be successfully passed into
 276  
      * the setter method.  Remember, Method.invoke() handles the unwrapping
 277  
      * of Integer into an int.
 278  
      * 
 279  
      * @param value The value to be passed into the setter method.
 280  
      * @param type The setter's parameter type.
 281  
      * @return boolean True if the value is compatible.
 282  
      */
 283  
     private boolean isCompatibleType(Object value, Class type) {
 284  
         // Do object check first, then primitives
 285  0
         if (value == null || type.isInstance(value)) {
 286  0
             return true;
 287  
 
 288  0
         } else if (
 289  0
             type.equals(Integer.TYPE) && Integer.class.isInstance(value)) {
 290  0
             return true;
 291  
 
 292  0
         } else if (type.equals(Long.TYPE) && Long.class.isInstance(value)) {
 293  0
             return true;
 294  
 
 295  0
         } else if (
 296  
             type.equals(Double.TYPE) && Double.class.isInstance(value)) {
 297  0
             return true;
 298  
 
 299  0
         } else if (type.equals(Float.TYPE) && Float.class.isInstance(value)) {
 300  0
             return true;
 301  
 
 302  0
         } else if (type.equals(Short.TYPE) && Short.class.isInstance(value)) {
 303  0
             return true;
 304  
 
 305  0
         } else if (type.equals(Byte.TYPE) && Byte.class.isInstance(value)) {
 306  0
             return true;
 307  
 
 308  0
         } else if (
 309  
             type.equals(Character.TYPE) && Character.class.isInstance(value)) {
 310  0
             return true;
 311  
 
 312  0
         } else if (
 313  
             type.equals(Boolean.TYPE) && Boolean.class.isInstance(value)) {
 314  0
             return true;
 315  
 
 316  
         } else {
 317  0
             return false;
 318  
         }
 319  
 
 320  
     }
 321  
 
 322  
     /**
 323  
      * Factory method that returns a new instance of the given Class.  This
 324  
      * is called at the start of the bean creation process and may be 
 325  
      * overridden to provide custom behavior like returning a cached bean
 326  
      * instance.
 327  
      *
 328  
      * @param c The Class to create an object from.
 329  
      * @return A newly created object of the Class.
 330  
      * @throws SQLException if creation failed.
 331  
      */
 332  
     protected Object newInstance(Class c) throws SQLException {
 333  
         try {
 334  0
             return c.newInstance();
 335  
 
 336  0
         } catch (InstantiationException e) {
 337  0
             throw new SQLException(
 338  
                 "Cannot create " + c.getName() + ": " + e.getMessage());
 339  
 
 340  0
         } catch (IllegalAccessException e) {
 341  0
             throw new SQLException(
 342  
                 "Cannot create " + c.getName() + ": " + e.getMessage());
 343  
         }
 344  
     }
 345  
 
 346  
     /**
 347  
      * Returns a PropertyDescriptor[] for the given Class.
 348  
      *
 349  
      * @param c The Class to retrieve PropertyDescriptors for.
 350  
      * @return A PropertyDescriptor[] describing the Class.
 351  
      * @throws SQLException if introspection failed.
 352  
      */
 353  
     private PropertyDescriptor[] propertyDescriptors(Class c)
 354  
         throws SQLException {
 355  
         // Introspector caches BeanInfo classes for better performance
 356  0
         BeanInfo beanInfo = null;
 357  
         try {
 358  0
             beanInfo = Introspector.getBeanInfo(c);
 359  
 
 360  0
         } catch (IntrospectionException e) {
 361  0
             throw new SQLException(
 362  
                 "Bean introspection failed: " + e.getMessage());
 363  0
         }
 364  
 
 365  0
         return beanInfo.getPropertyDescriptors();
 366  
     }
 367  
 
 368  
     /**
 369  
      * The positions in the returned array represent column numbers.  The 
 370  
      * values stored at each position represent the index in the 
 371  
      * <code>PropertyDescriptor[]</code> for the bean property that matches 
 372  
      * the column name.  If no bean property was found for a column, the 
 373  
      * position is set to <code>PROPERTY_NOT_FOUND</code>.
 374  
      * 
 375  
      * @param rsmd The <code>ResultSetMetaData</code> containing column 
 376  
      * information.
 377  
      * 
 378  
      * @param props The bean property descriptors.
 379  
      * 
 380  
      * @throws SQLException if a database access error occurs
 381  
      *
 382  
      * @return An int[] with column index to property index mappings.  The 0th 
 383  
      * element is meaningless because JDBC column indexing starts at 1.
 384  
      */
 385  
     protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
 386  
             PropertyDescriptor[] props) throws SQLException {
 387  
 
 388  0
         int cols = rsmd.getColumnCount();
 389  0
         int columnToProperty[] = new int[cols + 1];
 390  0
         Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
 391  
 
 392  0
         for (int col = 1; col <= cols; col++) {
 393  0
             String columnName = rsmd.getColumnName(col);
 394  0
             for (int i = 0; i < props.length; i++) {
 395  
 
 396  0
                 if (columnName.equalsIgnoreCase(props[i].getName())) {
 397  0
                     columnToProperty[col] = i;
 398  0
                     break;
 399  
                 }
 400  
             }
 401  
         }
 402  
 
 403  0
         return columnToProperty;
 404  
     }
 405  
 
 406  
     /**
 407  
      * Convert a <code>ResultSet</code> column into an object.  Simple 
 408  
      * implementations could just call <code>rs.getObject(index)</code> while
 409  
      * more complex implementations could perform type manipulation to match 
 410  
      * the column's type to the bean property type.
 411  
      * 
 412  
      * <p>
 413  
      * This implementation calls the appropriate <code>ResultSet</code> getter 
 414  
      * method for the given property type to perform the type conversion.  If 
 415  
      * the property type doesn't match one of the supported 
 416  
      * <code>ResultSet</code> types, <code>getObject</code> is called.
 417  
      * </p>
 418  
      * 
 419  
      * @param rs The <code>ResultSet</code> currently being processed.  It is
 420  
      * positioned on a valid row before being passed into this method.
 421  
      * 
 422  
      * @param index The current column index being processed.
 423  
      * 
 424  
      * @param propType The bean property type that this column needs to be
 425  
      * converted into.
 426  
      * 
 427  
      * @throws SQLException if a database access error occurs
 428  
      * 
 429  
      * @return The object from the <code>ResultSet</code> at the given column
 430  
      * index after optional type processing or <code>null</code> if the column
 431  
      * value was SQL NULL.
 432  
      */
 433  
     protected Object processColumn(ResultSet rs, int index, Class propType)
 434  
         throws SQLException {
 435  
 
 436  0
         if ( !propType.isPrimitive() && rs.getObject(index) == null ) {
 437  0
             return null;
 438  
         }
 439  
 
 440  0
         if (propType.equals(String.class)) {
 441  0
             return rs.getString(index);
 442  
 
 443  0
         } else if (
 444  
             propType.equals(Integer.TYPE) || propType.equals(Integer.class)) {
 445  0
             return new Integer(rs.getInt(index));
 446  
 
 447  0
         } else if (
 448  
             propType.equals(Boolean.TYPE) || propType.equals(Boolean.class)) {
 449  0
             return new Boolean(rs.getBoolean(index));
 450  
 
 451  0
         } else if (propType.equals(Long.TYPE) || propType.equals(Long.class)) {
 452  0
             return new Long(rs.getLong(index));
 453  
 
 454  0
         } else if (
 455  
             propType.equals(Double.TYPE) || propType.equals(Double.class)) {
 456  0
             return new Double(rs.getDouble(index));
 457  
 
 458  0
         } else if (
 459  
             propType.equals(Float.TYPE) || propType.equals(Float.class)) {
 460  0
             return new Float(rs.getFloat(index));
 461  
 
 462  0
         } else if (
 463  
             propType.equals(Short.TYPE) || propType.equals(Short.class)) {
 464  0
             return new Short(rs.getShort(index));
 465  
 
 466  0
         } else if (propType.equals(Byte.TYPE) || propType.equals(Byte.class)) {
 467  0
             return new Byte(rs.getByte(index));
 468  
 
 469  0
         } else if (propType.equals(Timestamp.class)) {
 470  0
             return rs.getTimestamp(index);
 471  
 
 472  
         } else {
 473  0
             return rs.getObject(index);
 474  
         }
 475  
 
 476  
     }
 477  
 
 478  
 }