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