dgraham     2004/03/14 15:03:54

  Modified:    dbutils/src/test/org/apache/commons/dbutils
                        BaseTestCase.java BasicRowProcessorTest.java
                        TestBean.java
               dbutils/src/java/org/apache/commons/dbutils
                        BasicRowProcessor.java
  Added:       dbutils/src/test/org/apache/commons/dbutils
                        BeanProcessorTest.java
               dbutils/src/java/org/apache/commons/dbutils
                        BeanProcessor.java
  Removed:     dbutils/src/test/org/apache/commons/dbutils
                        BasicColumnProcessorTest.java
               dbutils/src/java/org/apache/commons/dbutils
                        BasicColumnProcessor.java ColumnProcessor.java
  Log:
  Removed ColumnProcessor and BasicColumnProcessor and replaced

  with a new BeanProcessor class.  Rather than splitting bean logic

  across several classes it's now encapsulated in BeanProcessor.  Further,

  The ColumnProcessor interface only applied to beans which didn't fit with

  the other ResultSet transformations (ie. arrays, maps).  

  BasicRowProcessor now delegates to a BeanProcessor instance making

  its implementation clearer.
  
  Revision  Changes    Path
  1.8       +1 -1      
jakarta-commons/dbutils/src/test/org/apache/commons/dbutils/BaseTestCase.java
  
  Index: BaseTestCase.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-commons/dbutils/src/test/org/apache/commons/dbutils/BaseTestCase.java,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- BaseTestCase.java 9 Mar 2004 03:05:51 -0000       1.7
  +++ BaseTestCase.java 14 Mar 2004 23:03:54 -0000      1.8
  @@ -139,7 +139,7 @@
           TestSuite suite = new TestSuite("All DbUtils Tests");
   
           suite.addTestSuite(BasicRowProcessorTest.class);
  -        suite.addTestSuite(BasicColumnProcessorTest.class);
  +        suite.addTestSuite(BeanProcessorTest.class);
           suite.addTestSuite(ProxyFactoryTest.class);
           suite.addTestSuite(ResultSetIteratorTest.class);
           suite.addTestSuite(QueryLoaderTest.class);
  
  
  
  1.7       +20 -4     
jakarta-commons/dbutils/src/test/org/apache/commons/dbutils/BasicRowProcessorTest.java
  
  Index: BasicRowProcessorTest.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-commons/dbutils/src/test/org/apache/commons/dbutils/BasicRowProcessorTest.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- BasicRowProcessorTest.java        28 Feb 2004 00:12:22 -0000      1.6
  +++ BasicRowProcessorTest.java        14 Mar 2004 23:03:54 -0000      1.7
  @@ -16,6 +16,9 @@
   package org.apache.commons.dbutils;
   
   import java.sql.SQLException;
  +import java.text.DateFormat;
  +import java.text.ParseException;
  +import java.text.SimpleDateFormat;
   import java.util.List;
   import java.util.Map;
   
  @@ -25,6 +28,13 @@
   public class BasicRowProcessorTest extends BaseTestCase {
   
       private static final RowProcessor processor = new BasicRowProcessor();
  +    
  +    /**
  +     * Format that matches Date.toString().
  +     * Sun Mar 14 15:19:15 MST 2004
  +     */ 
  +    private static final DateFormat datef =
  +        new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
   
       /**
        * Constructor for BasicRowProcessorTest.
  @@ -50,7 +60,7 @@
           assertEquals("6", a[2]);
       }
   
  -    public void testToBean() throws SQLException {
  +    public void testToBean() throws SQLException, ParseException {
   
           int rowCount = 0;
           TestBean b = null;
  @@ -69,10 +79,13 @@
           assertEquals(new Integer(4), b.getIntegerTest());
           assertEquals(null, b.getNullObjectTest());
           assertEquals(0, b.getNullPrimitiveTest());
  -        assertEquals("not a date", b.getNotDate());
  +        // test date -> string handling
  +        assertNotNull(b.getNotDate());
  +        assertTrue(!"not a date".equals(b.getNotDate()));
  +        datef.parse(b.getNotDate());
       }
   
  -    public void testToBeanList() throws SQLException {
  +    public void testToBeanList() throws SQLException, ParseException {
   
           List list = processor.toBeanList(this.rs, TestBean.class);
           assertNotNull(list);
  @@ -88,7 +101,10 @@
           assertEquals(new Integer(4), b.getIntegerTest());
           assertEquals(null, b.getNullObjectTest());
           assertEquals(0, b.getNullPrimitiveTest());
  -        assertEquals("not a date", b.getNotDate());
  +        // test date -> string handling
  +        assertNotNull(b.getNotDate());
  +        assertTrue(!"not a date".equals(b.getNotDate()));
  +        datef.parse(b.getNotDate());
       }
   
       public void testToMap() throws SQLException {
  
  
  
  1.7       +2 -3      
jakarta-commons/dbutils/src/test/org/apache/commons/dbutils/TestBean.java
  
  Index: TestBean.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-commons/dbutils/src/test/org/apache/commons/dbutils/TestBean.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- TestBean.java     28 Feb 2004 00:12:22 -0000      1.6
  +++ TestBean.java     14 Mar 2004 23:03:54 -0000      1.7
  @@ -45,9 +45,8 @@
       private Object nullObjectTest = "overwrite";
   
       /**
  -     * The property should not be set when the object returned from the 
  -     * ResultSet does not match the type of the bean property.  In this case, 
  -     * a Date will be returned but the property is a String.
  +     * A Date will be returned from the ResultSet but the property is a String.
  +     * BeanProcessor should create a String from the Date and set this property.
        */
       private String notDate = "not a date";
       
  
  
  
  1.1                  
jakarta-commons/dbutils/src/test/org/apache/commons/dbutils/BeanProcessorTest.java
  
  Index: BeanProcessorTest.java
  ===================================================================
  /*
   * Copyright 2003-2004 The Apache Software Foundation
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
   
  package org.apache.commons.dbutils;
  
  import java.sql.SQLException;
  
  /**
   * BeanProcessorTest
   */
  public class BeanProcessorTest extends BaseTestCase {
  
      private static final BeanProcessor beanProc = new BeanProcessor();
  
      /**
       * Constructor for BasicColumnProcessorTest.
       * @param name
       */
      public BeanProcessorTest(String name) {
          super(name);
      }
  
      public void testProcess() throws SQLException {
          int rowCount = 0;
          TestBean b = null;
          while (this.rs.next()) {
              b = (TestBean) beanProc.toBean(this.rs, TestBean.class);
              assertNotNull(b);
              rowCount++;
          }
  
          assertEquals(ROWS, rowCount);
          assertEquals(13.0, b.getColumnProcessorDoubleTest(), 0);
      }
  
  }
  
  
  
  1.10      +17 -310   
jakarta-commons/dbutils/src/java/org/apache/commons/dbutils/BasicRowProcessor.java
  
  Index: BasicRowProcessor.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-commons/dbutils/src/java/org/apache/commons/dbutils/BasicRowProcessor.java,v
  retrieving revision 1.9
  retrieving revision 1.10
  diff -u -r1.9 -r1.10
  --- BasicRowProcessor.java    28 Feb 2004 00:12:23 -0000      1.9
  +++ BasicRowProcessor.java    14 Mar 2004 23:03:54 -0000      1.10
  @@ -15,16 +15,9 @@
    */
   package org.apache.commons.dbutils;
   
  -import java.beans.BeanInfo;
  -import java.beans.IntrospectionException;
  -import java.beans.Introspector;
  -import java.beans.PropertyDescriptor;
  -import java.lang.reflect.InvocationTargetException;
  -import java.lang.reflect.Method;
   import java.sql.ResultSet;
   import java.sql.ResultSetMetaData;
   import java.sql.SQLException;
  -import java.util.ArrayList;
   import java.util.HashMap;
   import java.util.Iterator;
   import java.util.List;
  @@ -38,27 +31,14 @@
    * </p>
    * 
    * @see RowProcessor
  - * @see ColumnProcessor
    */
   public class BasicRowProcessor implements RowProcessor {
   
       /**
  -     * Set a bean's primitive properties to these defaults when SQL NULL 
  -     * is returned.  These are the same as the defaults that ResultSet get* 
  -     * methods return in the event of a NULL column.
  +     * The default BeanProcessor instance to use if not supplied in the
  +     * constructor.
        */
  -    private static final Map primitiveDefaults = new HashMap();
  -
  -    static {
  -        primitiveDefaults.put(Integer.TYPE, new Integer(0));
  -        primitiveDefaults.put(Short.TYPE, new Short((short) 0));
  -        primitiveDefaults.put(Byte.TYPE, new Byte((byte) 0));
  -        primitiveDefaults.put(Float.TYPE, new Float(0));
  -        primitiveDefaults.put(Double.TYPE, new Double(0));
  -        primitiveDefaults.put(Long.TYPE, new Long(0));
  -        primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
  -        primitiveDefaults.put(Character.TYPE, new Character('\u0000'));
  -    }
  +    private static final BeanProcessor defaultConvert = new BeanProcessor();
   
       /**
        * The Singleton instance of this class.
  @@ -75,51 +55,27 @@
       public static BasicRowProcessor instance() {
           return instance;
       }
  -    
  -    /**
  -     * A basic ColumnProcessor implementation to use when client doesn't pass
  -     * one into the constructor.
  -     */
  -    private static final ColumnProcessor defaultProcessor =
  -        new ColumnProcessor() {
  -
  -        private final ColumnProcessor p = new BasicColumnProcessor();
  -
  -        public Object process(ResultSet rs, int index, Class propType)
  -            throws SQLException {
  -            return rs.getObject(index);
  -        }
  -
  -        public int[] mapColumnsToProperties(
  -            ResultSetMetaData rsmd,
  -            PropertyDescriptor[] props)
  -            throws SQLException {
  -
  -            // TODO Compose the default behavior from BasicColumnProcessor.  
  -            // Maybe the default should just be a BasicColumnProcessor?             
   
  -            return p.mapColumnsToProperties(rsmd, props);
  -        }
  -    };
   
       /**
  -     * Use this processor to convert columns to bean properties.
  +     * Use this to process beans.
        */
  -    private ColumnProcessor convert = defaultProcessor; 
  +    private BeanProcessor convert = null;
   
       /**
  -     * BasicRowProcessor constructor.
  +     * BasicRowProcessor constructor.  Bean processing defaults to a 
  +     * BeanProcessor instance.
        */
       public BasicRowProcessor() {
  -        super();
  +        this(defaultConvert);
       }
       
       /**
        * BasicRowProcessor constructor.
  -     * @param convert The ColumnProcessor to use when converting columns to 
  +     * @param convert The BeanProcessor to use when converting columns to 
        * bean properties.
        * @since DbUtils 1.1
        */
  -    public BasicRowProcessor(ColumnProcessor convert) {
  +    public BasicRowProcessor(BeanProcessor convert) {
           super();
           this.convert = convert;
       }
  @@ -146,137 +102,22 @@
   
       /**
        * Convert a <code>ResultSet</code> row into a JavaBean.  This 
  -     * implementation uses reflection and <code>BeanInfo</code> classes to 
  -     * match column names to bean property names.  Properties are matched to 
  -     * columns based on several factors:
  -     * <br/>
  -     * <ol>
  -     *     <li>
  -     *     The class has a writable property with the same name as a column.
  -     *     The name comparison is case insensitive.
  -     *     </li>
  -     * 
  -     *     <li>
  -     *     The property's set method parameter type matches the column 
  -     *     type. If the data types do not match, the setter will not be called.
  -     *     </li>
  -     * </ol>
  -     * 
  -     * <p>
  -     * Primitive bean properties are set to their defaults when SQL NULL is
  -     * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
  -     * and booleans are set to false.  Object bean properties are set to 
  -     * <code>null</code> when SQL NULL is returned.  This is the same behavior
  -     * as the <code>ResultSet</code> get* methods.
  -     * </p>
  -     * 
  +     * implementation delegates to a BeanProcessor instance.
  +     * @see org.apache.commons.dbutils.BeanProcessor#toBean(java.sql.ResultSet, 
java.lang.Class) 
        * @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet, 
java.lang.Class)
        */
       public Object toBean(ResultSet rs, Class type) throws SQLException {
  -
  -        PropertyDescriptor[] props = this.propertyDescriptors(type);
  -
  -        ResultSetMetaData rsmd = rs.getMetaData();
  -
  -        int[] columnToProperty =
  -            this.convert.mapColumnsToProperties(rsmd, props);
  -
  -        int cols = rsmd.getColumnCount();
  -
  -        return this.createBean(rs, type, props, columnToProperty, cols);
  +        return this.convert.toBean(rs, type);
       }
   
       /**
        * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans.  
  -     * This implementation uses reflection and <code>BeanInfo</code> classes to 
  -     * match column names to bean property names. Properties are matched to 
  -     * columns based on several factors:
  -     * <br/>
  -     * <ol>
  -     *     <li>
  -     *     The class has a writable property with the same name as a column.
  -     *     The name comparison is case insensitive.
  -     *     </li>
  -     * 
  -     *     <li>
  -     *     The property's set method parameter type matches the column 
  -     *     type. If the data types do not match, the setter will not be called.
  -     *     </li>
  -     * </ol>
  -     * 
  -     * <p>
  -     * Primitive bean properties are set to their defaults when SQL NULL is
  -     * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
  -     * and booleans are set to false.  Object bean properties are set to 
  -     * <code>null</code> when SQL NULL is returned.  This is the same behavior
  -     * as the <code>ResultSet</code> get* methods.
  -     * </p>
  -     * 
  +     * This implementation delegates to a BeanProcessor instance. 
  +     * @see org.apache.commons.dbutils.BeanProcessor#toBeanList(java.sql.ResultSet, 
java.lang.Class)
        * @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet, 
java.lang.Class)
        */
       public List toBeanList(ResultSet rs, Class type) throws SQLException {
  -        List results = new ArrayList();
  -
  -        if (!rs.next()) {
  -            return results;
  -        }
  -
  -        PropertyDescriptor[] props = this.propertyDescriptors(type);
  -        ResultSetMetaData rsmd = rs.getMetaData();
  -        
  -        int[] columnToProperty =
  -            this.convert.mapColumnsToProperties(rsmd, props);
  -            
  -        int cols = rsmd.getColumnCount();
  -
  -        do {
  -            results.add(this.createBean(rs, type, props, columnToProperty, cols));
  -
  -        } while (rs.next());
  -
  -        return results;
  -    }
  -
  -    /**
  -     * Creates a new object and initializes its fields from the ResultSet.
  -     *
  -     * @param rs The result set
  -     * @param type The bean type (the return type of the object)
  -     * @param props The property descriptors
  -     * @param columnToProperty The column indices in the result set
  -     * @param cols The number of columns
  -     * @return An initialized object.
  -     * @throws SQLException If a database error occurs
  -     */
  -    private Object createBean(
  -        ResultSet rs,
  -        Class type,
  -        PropertyDescriptor[] props,
  -        int[] columnToProperty,
  -        int cols)
  -        throws SQLException {
  -
  -        Object bean = this.newInstance(type);
  -
  -        for (int i = 1; i <= cols; i++) {
  -
  -            if (columnToProperty[i] == ColumnProcessor.PROPERTY_NOT_FOUND) {
  -                continue;
  -            }
  -            
  -            PropertyDescriptor prop = props[columnToProperty[i]];
  -            Class propType = prop.getPropertyType();
  -
  -            Object value = this.convert.process(rs, i, propType);
  -
  -            if (propType != null && value == null && propType.isPrimitive()) {
  -                value = primitiveDefaults.get(propType);
  -            }
  -
  -            this.callSetter(bean, prop, value);
  -        }
  -
  -        return bean;
  +        return this.convert.toBeanList(rs, type);
       }
   
       /**
  @@ -297,141 +138,7 @@
   
           return result;
       }
  -
  -    /**
  -     * Calls the setter method on the target object for the given property.
  -     * If no setter method exists for the property, this method does nothing.
  -     * @param target The object to set the property on.
  -     * @param prop The property to set.
  -     * @param value The value to pass into the setter.
  -     * @throws SQLException if an error occurs setting the property.
  -     */
  -    private void callSetter(
  -        Object target,
  -        PropertyDescriptor prop,
  -        Object value)
  -        throws SQLException {
  -
  -        Method setter = prop.getWriteMethod();
  -
  -        if (setter == null) {
  -            return;
  -        }
  -
  -        Class[] params = setter.getParameterTypes();
  -        try {
  -            // Don't call setter if the value object isn't the right type 
  -            if (this.isCompatibleType(value, params[0])) {
  -                setter.invoke(target, new Object[] { value });
  -            }
  -
  -        } catch (IllegalArgumentException e) {
  -            throw new SQLException(
  -                "Cannot set " + prop.getName() + ": " + e.getMessage());
  -
  -        } catch (IllegalAccessException e) {
  -            throw new SQLException(
  -                "Cannot set " + prop.getName() + ": " + e.getMessage());
  -
  -        } catch (InvocationTargetException e) {
  -            throw new SQLException(
  -                "Cannot set " + prop.getName() + ": " + e.getMessage());
  -        }
  -    }
  -
  -    /**
  -     * ResultSet.getObject() returns an Integer object for an INT column.  The
  -     * setter method for the property might take an Integer or a primitive int.
  -     * This method returns true if the value can be successfully passed into
  -     * the setter method.  Remember, Method.invoke() handles the unwrapping
  -     * of Integer into an int.
  -     * 
  -     * @param value The value to be passed into the setter method.
  -     * @param type The setter's parameter type.
  -     * @return boolean True if the value is compatible.
  -     */
  -    private boolean isCompatibleType(Object value, Class type) {
  -        // Do object check first, then primitives
  -        if (value == null || type.isInstance(value)) {
  -            return true;
  -
  -        } else if (
  -            type.equals(Integer.TYPE) && Integer.class.isInstance(value)) {
  -            return true;
  -
  -        } else if (type.equals(Long.TYPE) && Long.class.isInstance(value)) {
  -            return true;
  -
  -        } else if (
  -            type.equals(Double.TYPE) && Double.class.isInstance(value)) {
  -            return true;
  -
  -        } else if (type.equals(Float.TYPE) && Float.class.isInstance(value)) {
  -            return true;
  -
  -        } else if (type.equals(Short.TYPE) && Short.class.isInstance(value)) {
  -            return true;
  -
  -        } else if (type.equals(Byte.TYPE) && Byte.class.isInstance(value)) {
  -            return true;
  -
  -        } else if (
  -            type.equals(Character.TYPE) && Character.class.isInstance(value)) {
  -            return true;
  -
  -        } else if (
  -            type.equals(Boolean.TYPE) && Boolean.class.isInstance(value)) {
  -            return true;
  -
  -        } else {
  -            return false;
  -        }
  -
  -    }
  -
  -    /**
  -     * Returns a new instance of the given Class.
  -     *
  -     * @param c The Class to create an object from.
  -     * @return A newly created object of the Class.
  -     * @throws SQLException if creation failed.
  -     */
  -    private Object newInstance(Class c) throws SQLException {
  -        try {
  -            return c.newInstance();
  -
  -        } catch (InstantiationException e) {
  -            throw new SQLException(
  -                "Cannot create " + c.getName() + ": " + e.getMessage());
  -
  -        } catch (IllegalAccessException e) {
  -            throw new SQLException(
  -                "Cannot create " + c.getName() + ": " + e.getMessage());
  -        }
  -    }
  -
  -    /**
  -     * Returns a PropertyDescriptor[] for the given Class.
  -     *
  -     * @param c The Class to retrieve PropertyDescriptors for.
  -     * @return A PropertyDescriptor[] describing the Class.
  -     * @throws SQLException if introspection failed.
  -     */
  -    private PropertyDescriptor[] propertyDescriptors(Class c)
  -        throws SQLException {
  -        // Introspector caches BeanInfo classes for better performance
  -        BeanInfo beanInfo = null;
  -        try {
  -            beanInfo = Introspector.getBeanInfo(c);
  -
  -        } catch (IntrospectionException e) {
  -            throw new SQLException(
  -                "Bean introspection failed: " + e.getMessage());
  -        }
  -
  -        return beanInfo.getPropertyDescriptors();
  -    }
  -
  +    
       /**
        * A Map that converts all keys to lowercase Strings for case insensitive
        * lookups.  This is needed for the toMap() implementation because 
  
  
  
  1.1                  
jakarta-commons/dbutils/src/java/org/apache/commons/dbutils/BeanProcessor.java
  
  Index: BeanProcessor.java
  ===================================================================
  /*
   * Copyright 2002-2004 The Apache Software Foundation
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
   
  package org.apache.commons.dbutils;
  
  import java.beans.BeanInfo;
  import java.beans.IntrospectionException;
  import java.beans.Introspector;
  import java.beans.PropertyDescriptor;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.sql.ResultSet;
  import java.sql.ResultSetMetaData;
  import java.sql.SQLException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  /**
   * <code>BeanProcessor</code> matches column names to bean property names 
   * and converts <code>ResultSet</code> columns into objects for those bean 
   * properties.  Subclasses should override the methods in the processing chain
   * to customize behavior.
   * 
   * @see BasicRowProcessor
   * 
   * @since DbUtils 1.1
   */
  public class BeanProcessor {
      
      /**
       * Special array value used by <code>mapColumnsToProperties</code> that 
       * indicates there is no bean property that matches a column from a 
       * <code>ResultSet</code>.
       */
      protected static final int PROPERTY_NOT_FOUND = -1;
      
      /**
       * Set a bean's primitive properties to these defaults when SQL NULL 
       * is returned.  These are the same as the defaults that ResultSet get* 
       * methods return in the event of a NULL column.
       */
      private static final Map primitiveDefaults = new HashMap();
  
      static {
          primitiveDefaults.put(Integer.TYPE, new Integer(0));
          primitiveDefaults.put(Short.TYPE, new Short((short) 0));
          primitiveDefaults.put(Byte.TYPE, new Byte((byte) 0));
          primitiveDefaults.put(Float.TYPE, new Float(0));
          primitiveDefaults.put(Double.TYPE, new Double(0));
          primitiveDefaults.put(Long.TYPE, new Long(0));
          primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
          primitiveDefaults.put(Character.TYPE, new Character('\u0000'));
      }
  
      /**
       * Constructor for BeanProcessor.
       */
      public BeanProcessor() {
          super();
      }
      
      /**
       * Convert a <code>ResultSet</code> row into a JavaBean.  This 
       * implementation uses reflection and <code>BeanInfo</code> classes to 
       * match column names to bean property names.  Properties are matched to 
       * columns based on several factors:
       * <br/>
       * <ol>
       *     <li>
       *     The class has a writable property with the same name as a column.
       *     The name comparison is case insensitive.
       *     </li>
       * 
       *     <li>
       *     The column type can be converted to the property's set method 
       *     parameter type with a ResultSet.get* method.  If the conversion fails
       *     (ie. the property was an int and the column was a Timestamp) an
       *     SQLException is thrown.
       *     </li>
       * </ol>
       * 
       * <p>
       * Primitive bean properties are set to their defaults when SQL NULL is
       * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
       * and booleans are set to false.  Object bean properties are set to 
       * <code>null</code> when SQL NULL is returned.  This is the same behavior
       * as the <code>ResultSet</code> get* methods.
       * </p>
       */
      public Object toBean(ResultSet rs, Class type) throws SQLException {
  
          PropertyDescriptor[] props = this.propertyDescriptors(type);
  
          ResultSetMetaData rsmd = rs.getMetaData();
  
          int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
  
          int cols = rsmd.getColumnCount();
  
          return this.createBean(rs, type, props, columnToProperty, cols);
      }
      
      /**
       * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans.  
       * This implementation uses reflection and <code>BeanInfo</code> classes to 
       * match column names to bean property names. Properties are matched to 
       * columns based on several factors:
       * <br/>
       * <ol>
       *     <li>
       *     The class has a writable property with the same name as a column.
       *     The name comparison is case insensitive.
       *     </li>
       * 
       *     <li>
       *     The column type can be converted to the property's set method 
       *     parameter type with a ResultSet.get* method.  If the conversion fails
       *     (ie. the property was an int and the column was a Timestamp) an
       *     SQLException is thrown.
       *     </li>
       * </ol>
       * 
       * <p>
       * Primitive bean properties are set to their defaults when SQL NULL is
       * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
       * and booleans are set to false.  Object bean properties are set to 
       * <code>null</code> when SQL NULL is returned.  This is the same behavior
       * as the <code>ResultSet</code> get* methods.
       * </p>
       */
      public List toBeanList(ResultSet rs, Class type) throws SQLException {
          List results = new ArrayList();
  
          if (!rs.next()) {
              return results;
          }
  
          PropertyDescriptor[] props = this.propertyDescriptors(type);
          ResultSetMetaData rsmd = rs.getMetaData();
          
          int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
              
          int cols = rsmd.getColumnCount();
  
          do {
              results.add(this.createBean(rs, type, props, columnToProperty, cols));
  
          } while (rs.next());
  
          return results;
      }
      
      /**
       * Creates a new object and initializes its fields from the ResultSet.
       *
       * @param rs The result set
       * @param type The bean type (the return type of the object)
       * @param props The property descriptors
       * @param columnToProperty The column indices in the result set
       * @param cols The number of columns
       * @return An initialized object.
       * @throws SQLException If a database error occurs
       */
      private Object createBean(
          ResultSet rs,
          Class type,
          PropertyDescriptor[] props,
          int[] columnToProperty,
          int cols)
          throws SQLException {
  
          Object bean = this.newInstance(type);
  
          for (int i = 1; i <= cols; i++) {
  
              if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
                  continue;
              }
              
              PropertyDescriptor prop = props[columnToProperty[i]];
              Class propType = prop.getPropertyType();
  
              Object value = this.processColumn(rs, i, propType);
  
              if (propType != null && value == null && propType.isPrimitive()) {
                  value = primitiveDefaults.get(propType);
              }
  
              this.callSetter(bean, prop, value);
          }
  
          return bean;
      }
      
      /**
       * Calls the setter method on the target object for the given property.
       * If no setter method exists for the property, this method does nothing.
       * @param target The object to set the property on.
       * @param prop The property to set.
       * @param value The value to pass into the setter.
       * @throws SQLException if an error occurs setting the property.
       */
      private void callSetter(
          Object target,
          PropertyDescriptor prop,
          Object value)
          throws SQLException {
  
          Method setter = prop.getWriteMethod();
  
          if (setter == null) {
              return;
          }
  
          Class[] params = setter.getParameterTypes();
          try {
              // Don't call setter if the value object isn't the right type 
              if (this.isCompatibleType(value, params[0])) {
                  setter.invoke(target, new Object[] { value });
              }
  
          } catch (IllegalArgumentException e) {
              throw new SQLException(
                  "Cannot set " + prop.getName() + ": " + e.getMessage());
  
          } catch (IllegalAccessException e) {
              throw new SQLException(
                  "Cannot set " + prop.getName() + ": " + e.getMessage());
  
          } catch (InvocationTargetException e) {
              throw new SQLException(
                  "Cannot set " + prop.getName() + ": " + e.getMessage());
          }
      }
  
      /**
       * ResultSet.getObject() returns an Integer object for an INT column.  The
       * setter method for the property might take an Integer or a primitive int.
       * This method returns true if the value can be successfully passed into
       * the setter method.  Remember, Method.invoke() handles the unwrapping
       * of Integer into an int.
       * 
       * @param value The value to be passed into the setter method.
       * @param type The setter's parameter type.
       * @return boolean True if the value is compatible.
       */
      private boolean isCompatibleType(Object value, Class type) {
          // Do object check first, then primitives
          if (value == null || type.isInstance(value)) {
              return true;
  
          } else if (
              type.equals(Integer.TYPE) && Integer.class.isInstance(value)) {
              return true;
  
          } else if (type.equals(Long.TYPE) && Long.class.isInstance(value)) {
              return true;
  
          } else if (
              type.equals(Double.TYPE) && Double.class.isInstance(value)) {
              return true;
  
          } else if (type.equals(Float.TYPE) && Float.class.isInstance(value)) {
              return true;
  
          } else if (type.equals(Short.TYPE) && Short.class.isInstance(value)) {
              return true;
  
          } else if (type.equals(Byte.TYPE) && Byte.class.isInstance(value)) {
              return true;
  
          } else if (
              type.equals(Character.TYPE) && Character.class.isInstance(value)) {
              return true;
  
          } else if (
              type.equals(Boolean.TYPE) && Boolean.class.isInstance(value)) {
              return true;
  
          } else {
              return false;
          }
  
      }
  
      /**
       * Returns a new instance of the given Class.
       *
       * @param c The Class to create an object from.
       * @return A newly created object of the Class.
       * @throws SQLException if creation failed.
       */
      private Object newInstance(Class c) throws SQLException {
          try {
              return c.newInstance();
  
          } catch (InstantiationException e) {
              throw new SQLException(
                  "Cannot create " + c.getName() + ": " + e.getMessage());
  
          } catch (IllegalAccessException e) {
              throw new SQLException(
                  "Cannot create " + c.getName() + ": " + e.getMessage());
          }
      }
  
      /**
       * Returns a PropertyDescriptor[] for the given Class.
       *
       * @param c The Class to retrieve PropertyDescriptors for.
       * @return A PropertyDescriptor[] describing the Class.
       * @throws SQLException if introspection failed.
       */
      private PropertyDescriptor[] propertyDescriptors(Class c)
          throws SQLException {
          // Introspector caches BeanInfo classes for better performance
          BeanInfo beanInfo = null;
          try {
              beanInfo = Introspector.getBeanInfo(c);
  
          } catch (IntrospectionException e) {
              throw new SQLException(
                  "Bean introspection failed: " + e.getMessage());
          }
  
          return beanInfo.getPropertyDescriptors();
      }
      
      /**
       * The positions in the returned array represent column numbers.  The 
       * values stored at each position represent the index in the 
       * <code>PropertyDescriptor[]</code> for the bean property that matches 
       * the column name.  If no bean property was found for a column, the 
       * position is set to <code>PROPERTY_NOT_FOUND</code>.
       * 
       * @param rsmd The <code>ResultSetMetaData</code> containing column 
       * information.
       * 
       * @param props The bean property descriptors.
       * 
       * @return An int[] with column index to property index mappings.  The 0th 
       * element is meaningless because JDBC column indexing starts at 1.
       * 
       * @throws SQLException 
       */
      protected int[] mapColumnsToProperties(
          ResultSetMetaData rsmd,
          PropertyDescriptor[] props)
          throws SQLException {
  
          int cols = rsmd.getColumnCount();
          int columnToProperty[] = new int[cols + 1];
  
          for (int col = 1; col <= cols; col++) {
              String columnName = rsmd.getColumnName(col);
              for (int i = 0; i < props.length; i++) {
  
                  if (columnName.equalsIgnoreCase(props[i].getName())) {
                      columnToProperty[col] = i;
                      break;
  
                  } else {
                      columnToProperty[col] = PROPERTY_NOT_FOUND;
                  }
              }
          }
  
          return columnToProperty;
      }
  
      /**
       * Convert a <code>ResultSet</code> column into an object.  Simple 
       * implementations could just call <code>rs.getObject(index)</code> while
       * more complex implementations could perform type manipulation to match 
       * the column's type to the bean property type.
       * 
       * <p>
       * This implementation calls the appropriate <code>ResultSet</code> getter 
       * method for the given property type to perform the type conversion.  If 
       * the property type doesn't match one of the supported 
       * <code>ResultSet</code> types, <code>getObject</code> is called.
       * </p>
       * 
       * @param rs The <code>ResultSet</code> currently being processed.  It is
       * positioned on a valid row before being passed into this method.
       * 
       * @param index The current column index being processed.
       * 
       * @param propertyType The bean property type that this column needs to be
       * converted into.
       * 
       * @return The object from the <code>ResultSet</code> at the given column
       * index after optional type processing or <code>null</code> if the column
       * value was SQL NULL.
       * 
       * @throws SQLException
       */
      protected Object processColumn(ResultSet rs, int index, Class propType)
          throws SQLException {
  
          if (propType.equals(String.class)) {
              return rs.getString(index);
              
          } else if (
              propType.equals(Integer.TYPE) || propType.equals(Integer.class)) {
              return new Integer(rs.getInt(index));
  
          } else if (
              propType.equals(Boolean.TYPE) || propType.equals(Boolean.class)) {
              return new Boolean(rs.getBoolean(index));
  
          } else if (propType.equals(Long.TYPE) || propType.equals(Long.class)) {
              return new Long(rs.getLong(index));
  
          } else if (
              propType.equals(Double.TYPE) || propType.equals(Double.class)) {
              return new Double(rs.getDouble(index));
  
          } else if (
              propType.equals(Float.TYPE) || propType.equals(Float.class)) {
              return new Float(rs.getFloat(index));
  
          } else if (
              propType.equals(Short.TYPE) || propType.equals(Short.class)) {
              return new Short(rs.getShort(index));
  
          } else if (propType.equals(Byte.TYPE) || propType.equals(Byte.class)) {
              return new Byte(rs.getByte(index));
                          
          } else {
              return rs.getObject(index);
          }
  
      }
  
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to