001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.dbutils;
018    
019    import java.sql.ResultSet;
020    import java.sql.ResultSetMetaData;
021    import java.sql.SQLException;
022    import java.util.HashMap;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Map;
026    
027    /**
028     * Basic implementation of the <code>RowProcessor</code> interface.
029     * 
030     * <p>
031     * This class is thread-safe.
032     * </p>
033     * 
034     * @see RowProcessor
035     */
036    public class BasicRowProcessor implements RowProcessor {
037    
038        /**
039         * The default BeanProcessor instance to use if not supplied in the
040         * constructor.
041         */
042        private static final BeanProcessor defaultConvert = new BeanProcessor();
043    
044        /**
045         * The Singleton instance of this class.
046         */
047        private static final BasicRowProcessor instance = new BasicRowProcessor();
048    
049        /**
050         * Returns the Singleton instance of this class.
051         *
052         * @return The single instance of this class.
053         * @deprecated Create instances with the constructors instead.  This will 
054         * be removed after DbUtils 1.1.
055         */
056        public static BasicRowProcessor instance() {
057            return instance;
058        }
059    
060        /**
061         * Use this to process beans.
062         */
063        private final BeanProcessor convert;
064    
065        /**
066         * BasicRowProcessor constructor.  Bean processing defaults to a 
067         * BeanProcessor instance.
068         */
069        public BasicRowProcessor() {
070            this(defaultConvert);
071        }
072        
073        /**
074         * BasicRowProcessor constructor.
075         * @param convert The BeanProcessor to use when converting columns to 
076         * bean properties.
077         * @since DbUtils 1.1
078         */
079        public BasicRowProcessor(BeanProcessor convert) {
080            super();
081            this.convert = convert;
082        }
083    
084        /**
085         * Convert a <code>ResultSet</code> row into an <code>Object[]</code>.
086         * This implementation copies column values into the array in the same 
087         * order they're returned from the <code>ResultSet</code>.  Array elements
088         * will be set to <code>null</code> if the column was SQL NULL.
089         *
090         * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)
091         */
092        public Object[] toArray(ResultSet rs) throws SQLException {
093            ResultSetMetaData meta = rs.getMetaData();
094            int cols = meta.getColumnCount();
095            Object[] result = new Object[cols];
096    
097            for (int i = 0; i < cols; i++) {
098                result[i] = rs.getObject(i + 1);
099            }
100    
101            return result;
102        }
103    
104        /**
105         * Convert a <code>ResultSet</code> row into a JavaBean.  This 
106         * implementation delegates to a BeanProcessor instance.
107         * @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet, java.lang.Class)
108         * @see org.apache.commons.dbutils.BeanProcessor#toBean(java.sql.ResultSet, java.lang.Class) 
109         */
110        public Object toBean(ResultSet rs, Class type) throws SQLException {
111            return this.convert.toBean(rs, type);
112        }
113    
114        /**
115         * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans.  
116         * This implementation delegates to a BeanProcessor instance. 
117         * @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet, java.lang.Class)
118         * @see org.apache.commons.dbutils.BeanProcessor#toBeanList(java.sql.ResultSet, java.lang.Class)
119         */
120        public List toBeanList(ResultSet rs, Class type) throws SQLException {
121            return this.convert.toBeanList(rs, type);
122        }
123    
124        /**
125         * Convert a <code>ResultSet</code> row into a <code>Map</code>.  This 
126         * implementation returns a <code>Map</code> with case insensitive column
127         * names as keys.  Calls to <code>map.get("COL")</code> and 
128         * <code>map.get("col")</code> return the same value.
129         * @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet)
130         */
131        public Map toMap(ResultSet rs) throws SQLException {
132            Map result = new CaseInsensitiveHashMap();
133            ResultSetMetaData rsmd = rs.getMetaData();
134            int cols = rsmd.getColumnCount();
135    
136            for (int i = 1; i <= cols; i++) {
137                result.put(rsmd.getColumnName(i), rs.getObject(i));
138            }
139    
140            return result;
141        }
142        
143        /**
144         * A Map that converts all keys to lowercase Strings for case insensitive
145         * lookups.  This is needed for the toMap() implementation because 
146         * databases don't consistenly handle the casing of column names. 
147         * 
148         * <p>The keys are stored as they are given [BUG #DBUTILS-34], so we maintain
149         * an internal mapping from lowercase keys to the real keys in order to 
150         * achieve the case insensitive lookup.
151         * 
152         * <p>Note: This implementation does not allow <tt>null</tt>
153         * for key, whereas {@link HashMap} does, because of the code:
154         * <pre>
155         * key.toString().toLowerCase()
156         * </pre>
157         */
158        private static class CaseInsensitiveHashMap extends HashMap {
159    
160            /**
161             * The internal mapping from lowercase keys to the real keys.
162             * 
163             * <p>
164             * Any query operation using the key 
165             * ({@link #get(Object)}, {@link #containsKey(Object)})
166             * is done in three steps:
167             * <ul>
168             * <li>convert the parameter key to lower case</li>
169             * <li>get the actual key that corresponds to the lower case key</li>
170             * <li>query the map with the actual key</li>
171             * </ul>
172             * </p>
173             */
174            private final Map lowerCaseMap = new HashMap();
175    
176            /**
177             * Required for serialization support.
178             * 
179             * @see java.io.Serializable
180             */ 
181            private static final long serialVersionUID = 1841673097701957808L;
182    
183            /**
184             * @see java.util.Map#containsKey(java.lang.Object)
185             */
186            public boolean containsKey(Object key) {
187                Object realKey = lowerCaseMap.get(key.toString().toLowerCase());
188                return super.containsKey(realKey);
189                // Possible optimisation here:
190                // Since the lowerCaseMap contains a mapping for all the keys,
191                // we could just do this:
192                // return lowerCaseMap.containsKey(key.toString().toLowerCase());
193            }
194    
195            /**
196             * @see java.util.Map#get(java.lang.Object)
197             */
198            public Object get(Object key) {
199                Object realKey = lowerCaseMap.get(key.toString().toLowerCase());
200                return super.get(realKey);
201            }
202    
203            /**
204             * @see java.util.Map#put(java.lang.Object, java.lang.Object)
205             */
206            public Object put(Object key, Object value) {
207                /*
208                 * In order to keep the map and lowerCaseMap synchronized,
209                 * we have to remove the old mapping before putting the 
210                 * new one. Indeed, oldKey and key are not necessaliry equals.
211                 * (That's why we call super.remove(oldKey) and not just
212                 * super.put(key, value))
213                 */
214                Object oldKey = lowerCaseMap.put(key.toString().toLowerCase(), key);
215                Object oldValue = super.remove(oldKey);
216                super.put(key, value);
217                return oldValue;
218            }
219    
220            /**
221             * @see java.util.Map#putAll(java.util.Map)
222             */
223            public void putAll(Map m) {
224                Iterator iter = m.entrySet().iterator();
225                while (iter.hasNext()) {
226                    Map.Entry entry = (Map.Entry) iter.next();
227                    Object key = entry.getKey();
228                    Object value = entry.getValue();
229                    this.put(key, value);
230                }
231            }
232    
233            /**
234             * @see java.util.Map#remove(java.lang.Object)
235             */
236            public Object remove(Object key) {
237                Object realKey = lowerCaseMap.remove(key.toString().toLowerCase());
238                return super.remove(realKey);
239            }
240        }
241        
242    }