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.List;
024    import java.util.Map;
025    
026    /**
027     * Basic implementation of the <code>RowProcessor</code> interface.
028     * 
029     * <p>
030     * This class is thread-safe.
031     * </p>
032     * 
033     * @see RowProcessor
034     */
035    public class BasicRowProcessor implements RowProcessor {
036    
037        /**
038         * The default BeanProcessor instance to use if not supplied in the
039         * constructor.
040         */
041        private static final BeanProcessor defaultConvert = new BeanProcessor();
042    
043        /**
044         * The Singleton instance of this class.
045         */
046        private static final BasicRowProcessor instance = new BasicRowProcessor();
047    
048        /**
049         * Returns the Singleton instance of this class.
050         *
051         * @return The single instance of this class.
052         * @deprecated Create instances with the constructors instead.  This will 
053         * be removed after DbUtils 1.1.
054         */
055        public static BasicRowProcessor instance() {
056            return instance;
057        }
058    
059        /**
060         * Use this to process beans.
061         */
062        private final BeanProcessor convert;
063    
064        /**
065         * BasicRowProcessor constructor.  Bean processing defaults to a 
066         * BeanProcessor instance.
067         */
068        public BasicRowProcessor() {
069            this(defaultConvert);
070        }
071        
072        /**
073         * BasicRowProcessor constructor.
074         * @param convert The BeanProcessor to use when converting columns to 
075         * bean properties.
076         * @since DbUtils 1.1
077         */
078        public BasicRowProcessor(BeanProcessor convert) {
079            super();
080            this.convert = convert;
081        }
082    
083        /**
084         * Convert a <code>ResultSet</code> row into an <code>Object[]</code>.
085         * This implementation copies column values into the array in the same 
086         * order they're returned from the <code>ResultSet</code>.  Array elements
087         * will be set to <code>null</code> if the column was SQL NULL.
088         *
089         * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)
090         * @param rs ResultSet that supplies the array data
091         * @throws SQLException if a database access error occurs
092         * @return the newly created array
093         */
094        public Object[] toArray(ResultSet rs) throws SQLException {
095            ResultSetMetaData meta = rs.getMetaData();
096            int cols = meta.getColumnCount();
097            Object[] result = new Object[cols];
098    
099            for (int i = 0; i < cols; i++) {
100                result[i] = rs.getObject(i + 1);
101            }
102    
103            return result;
104        }
105    
106        /**
107         * Convert a <code>ResultSet</code> row into a JavaBean.  This 
108         * implementation delegates to a BeanProcessor instance.
109         * @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet, java.lang.Class)
110         * @see org.apache.commons.dbutils.BeanProcessor#toBean(java.sql.ResultSet, java.lang.Class)
111         * @param <T> The type of bean to create
112         * @param rs ResultSet that supplies the bean data
113         * @param type Class from which to create the bean instance
114         * @throws SQLException if a database access error occurs
115         * @return the newly created bean 
116         */
117        public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
118            return this.convert.toBean(rs, type);
119        }
120    
121        /**
122         * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans.  
123         * This implementation delegates to a BeanProcessor instance. 
124         * @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet, java.lang.Class)
125         * @see org.apache.commons.dbutils.BeanProcessor#toBeanList(java.sql.ResultSet, java.lang.Class)
126         * @param <T> The type of bean to create
127         * @param rs ResultSet that supplies the bean data
128         * @param type Class from which to create the bean instance
129         * @throws SQLException if a database access error occurs
130         * @return A <code>List</code> of beans with the given type in the order 
131         * they were returned by the <code>ResultSet</code>.
132         */
133        public <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException {
134            return this.convert.toBeanList(rs, type);
135        }
136    
137        /**
138         * Convert a <code>ResultSet</code> row into a <code>Map</code>.  This 
139         * implementation returns a <code>Map</code> with case insensitive column
140         * names as keys.  Calls to <code>map.get("COL")</code> and 
141         * <code>map.get("col")</code> return the same value.
142         * @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet)
143         * @param rs ResultSet that supplies the map data
144         * @throws SQLException if a database access error occurs
145         * @return the newly created Map
146         */
147        public Map<String, Object> toMap(ResultSet rs) throws SQLException {
148            Map<String, Object> result = new CaseInsensitiveHashMap();
149            ResultSetMetaData rsmd = rs.getMetaData();
150            int cols = rsmd.getColumnCount();
151    
152            for (int i = 1; i <= cols; i++) {
153                result.put(rsmd.getColumnName(i), rs.getObject(i));
154            }
155    
156            return result;
157        }
158        
159        /**
160         * A Map that converts all keys to lowercase Strings for case insensitive
161         * lookups.  This is needed for the toMap() implementation because 
162         * databases don't consistenly handle the casing of column names. 
163         * 
164         * <p>The keys are stored as they are given [BUG #DBUTILS-34], so we maintain
165         * an internal mapping from lowercase keys to the real keys in order to 
166         * achieve the case insensitive lookup.
167         * 
168         * <p>Note: This implementation does not allow <tt>null</tt>
169         * for key, whereas {@link HashMap} does, because of the code:
170         * <pre>
171         * key.toString().toLowerCase()
172         * </pre>
173         */
174        private static class CaseInsensitiveHashMap extends HashMap<String, Object> {
175            /**
176             * The internal mapping from lowercase keys to the real keys.
177             * 
178             * <p>
179             * Any query operation using the key 
180             * ({@link #get(Object)}, {@link #containsKey(Object)})
181             * is done in three steps:
182             * <ul>
183             * <li>convert the parameter key to lower case</li>
184             * <li>get the actual key that corresponds to the lower case key</li>
185             * <li>query the map with the actual key</li>
186             * </ul>
187             * </p>
188             */
189            private final Map<String,String> lowerCaseMap = new HashMap<String,String>();
190    
191            /**
192             * Required for serialization support.
193             * 
194             * @see java.io.Serializable
195             */ 
196            private static final long serialVersionUID = -2848100435296897392L;
197    
198            /** {@inheritDoc} */
199            @Override
200            public boolean containsKey(Object key) {
201                Object realKey = lowerCaseMap.get(key.toString().toLowerCase());
202                return super.containsKey(realKey);
203                // Possible optimisation here:
204                // Since the lowerCaseMap contains a mapping for all the keys,
205                // we could just do this:
206                // return lowerCaseMap.containsKey(key.toString().toLowerCase());
207            }
208    
209            /** {@inheritDoc} */
210            @Override
211            public Object get(Object key) {
212                Object realKey = lowerCaseMap.get(key.toString().toLowerCase());
213                return super.get(realKey);
214            }
215    
216            /** {@inheritDoc} */
217            @Override
218            public Object put(String key, Object value) {
219                /*
220                 * In order to keep the map and lowerCaseMap synchronized,
221                 * we have to remove the old mapping before putting the 
222                 * new one. Indeed, oldKey and key are not necessaliry equals.
223                 * (That's why we call super.remove(oldKey) and not just
224                 * super.put(key, value))
225                 */
226                Object oldKey = lowerCaseMap.put(key.toLowerCase(), key);
227                Object oldValue = super.remove(oldKey);
228                super.put(key, value);
229                return oldValue;
230            }
231    
232            /** {@inheritDoc} */
233            @Override
234            public void putAll(Map<? extends String,?> m) {
235                for (Map.Entry<? extends String, ?> entry : m.entrySet()) {
236                    String key = entry.getKey();
237                    Object value = entry.getValue();
238                    this.put(key, value);
239                }
240            }
241    
242            /** {@inheritDoc} */
243            @Override
244            public Object remove(Object key) {
245                Object realKey = lowerCaseMap.remove(key.toString().toLowerCase());
246                return super.remove(realKey);
247            }
248        }
249        
250    }