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 */
017package org.apache.commons.dbutils;
018
019import java.sql.ResultSet;
020import java.sql.ResultSetMetaData;
021import java.sql.SQLException;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Locale;
025import 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 */
036public 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            String columnName = rsmd.getColumnLabel(i);
160            if (null == columnName || 0 == columnName.length()) {
161              columnName = rsmd.getColumnName(i);
162            }
163            result.put(columnName, rs.getObject(i));
164        }
165
166        return result;
167    }
168
169    /**
170     * A Map that converts all keys to lowercase Strings for case insensitive
171     * lookups.  This is needed for the toMap() implementation because
172     * databases don't consistently handle the casing of column names.
173     *
174     * <p>The keys are stored as they are given [BUG #DBUTILS-34], so we maintain
175     * an internal mapping from lowercase keys to the real keys in order to
176     * achieve the case insensitive lookup.
177     *
178     * <p>Note: This implementation does not allow <tt>null</tt>
179     * for key, whereas {@link HashMap} does, because of the code:
180     * <pre>
181     * key.toString().toLowerCase()
182     * </pre>
183     */
184    private static class CaseInsensitiveHashMap extends HashMap<String, Object> {
185        /**
186         * The internal mapping from lowercase keys to the real keys.
187         *
188         * <p>
189         * Any query operation using the key
190         * ({@link #get(Object)}, {@link #containsKey(Object)})
191         * is done in three steps:
192         * <ul>
193         * <li>convert the parameter key to lower case</li>
194         * <li>get the actual key that corresponds to the lower case key</li>
195         * <li>query the map with the actual key</li>
196         * </ul>
197         * </p>
198         */
199        private final Map<String, String> lowerCaseMap = new HashMap<String, String>();
200
201        /**
202         * Required for serialization support.
203         *
204         * @see java.io.Serializable
205         */
206        private static final long serialVersionUID = -2848100435296897392L;
207
208        /** {@inheritDoc} */
209        @Override
210        public boolean containsKey(Object key) {
211            Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
212            return super.containsKey(realKey);
213            // Possible optimisation here:
214            // Since the lowerCaseMap contains a mapping for all the keys,
215            // we could just do this:
216            // return lowerCaseMap.containsKey(key.toString().toLowerCase());
217        }
218
219        /** {@inheritDoc} */
220        @Override
221        public Object get(Object key) {
222            Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
223            return super.get(realKey);
224        }
225
226        /** {@inheritDoc} */
227        @Override
228        public Object put(String key, Object value) {
229            /*
230             * In order to keep the map and lowerCaseMap synchronized,
231             * we have to remove the old mapping before putting the
232             * new one. Indeed, oldKey and key are not necessaliry equals.
233             * (That's why we call super.remove(oldKey) and not just
234             * super.put(key, value))
235             */
236            Object oldKey = lowerCaseMap.put(key.toLowerCase(Locale.ENGLISH), key);
237            Object oldValue = super.remove(oldKey);
238            super.put(key, value);
239            return oldValue;
240        }
241
242        /** {@inheritDoc} */
243        @Override
244        public void putAll(Map<? extends String, ?> m) {
245            for (Map.Entry<? extends String, ?> entry : m.entrySet()) {
246                String key = entry.getKey();
247                Object value = entry.getValue();
248                this.put(key, value);
249            }
250        }
251
252        /** {@inheritDoc} */
253        @Override
254        public Object remove(Object key) {
255            Object realKey = lowerCaseMap.remove(key.toString().toLowerCase(Locale.ENGLISH));
256            return super.remove(realKey);
257        }
258    }
259
260}