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