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