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