BasicRowProcessor.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.dbutils;

  18. import java.sql.ResultSet;
  19. import java.sql.ResultSetMetaData;
  20. import java.sql.SQLException;
  21. import java.util.HashMap;
  22. import java.util.LinkedHashMap;
  23. import java.util.List;
  24. import java.util.Locale;
  25. import java.util.Map;

  26. /**
  27.  * Basic implementation of the {@code RowProcessor} interface.
  28.  *
  29.  * <p>
  30.  * This class is thread-safe.
  31.  * </p>
  32.  *
  33.  * @see RowProcessor
  34.  */
  35. public class BasicRowProcessor implements RowProcessor {

  36.     /**
  37.      * A Map that converts all keys to lowercase Strings for case insensitive
  38.      * lookups.  This is needed for the toMap() implementation because
  39.      * databases don't consistently handle the casing of column names.
  40.      *
  41.      * <p>The keys are stored as they are given [BUG #DBUTILS-34], so we maintain
  42.      * an internal mapping from lowercase keys to the real keys in order to
  43.      * achieve the case insensitive lookup.
  44.      *
  45.      * <p>Note: This implementation does not allow {@code null}
  46.      * for key, whereas {@link LinkedHashMap} does, because of the code:
  47.      * <pre>
  48.      * key.toString().toLowerCase()
  49.      * </pre>
  50.      */
  51.     private static final class CaseInsensitiveHashMap extends LinkedHashMap<String, Object> {

  52.         /**
  53.          * Required for serialization support.
  54.          *
  55.          * @see java.io.Serializable
  56.          */
  57.         private static final long serialVersionUID = -2848100435296897392L;

  58.         /**
  59.          * The internal mapping from lowercase keys to the real keys.
  60.          *
  61.          * <p>
  62.          * Any query operation using the key
  63.          * ({@link #get(Object)}, {@link #containsKey(Object)})
  64.          * is done in three steps:
  65.          * <ul>
  66.          * <li>convert the parameter key to lower case</li>
  67.          * <li>get the actual key that corresponds to the lower case key</li>
  68.          * <li>query the map with the actual key</li>
  69.          * </ul>
  70.          * </p>
  71.          */
  72.         private final Map<String, String> lowerCaseMap = new HashMap<>();

  73.         private CaseInsensitiveHashMap(final int initialCapacity) {
  74.             super(initialCapacity);
  75.         }

  76.         /** {@inheritDoc} */
  77.         @Override
  78.         public boolean containsKey(final Object key) {
  79.             final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
  80.             return super.containsKey(realKey);
  81.             // Possible optimisation here:
  82.             // Since the lowerCaseMap contains a mapping for all the keys,
  83.             // we could just do this:
  84.             // return lowerCaseMap.containsKey(key.toString().toLowerCase());
  85.         }

  86.         /** {@inheritDoc} */
  87.         @Override
  88.         public Object get(final Object key) {
  89.             final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
  90.             return super.get(realKey);
  91.         }

  92.         /** {@inheritDoc} */
  93.         @Override
  94.         public Object put(final String key, final Object value) {
  95.             /*
  96.              * In order to keep the map and lowerCaseMap synchronized,
  97.              * we have to remove the old mapping before putting the
  98.              * new one. Indeed, oldKey and key are not necessarily equals.
  99.              * (That's why we call super.remove(oldKey) and not just
  100.              * super.put(key, value))
  101.              */
  102.             final Object oldKey = lowerCaseMap.put(key.toLowerCase(Locale.ENGLISH), key);
  103.             final Object oldValue = super.remove(oldKey);
  104.             super.put(key, value);
  105.             return oldValue;
  106.         }

  107.         /** {@inheritDoc} */
  108.         @Override
  109.         public void putAll(final Map<? extends String, ?> m) {
  110.             m.forEach(this::put);
  111.         }

  112.         /** {@inheritDoc} */
  113.         @Override
  114.         public Object remove(final Object key) {
  115.             final Object realKey = lowerCaseMap.remove(key.toString().toLowerCase(Locale.ENGLISH));
  116.             return super.remove(realKey);
  117.         }
  118.     }

  119.     /**
  120.      * The default BeanProcessor instance to use if not supplied in the
  121.      * constructor.
  122.      */
  123.     private static final BeanProcessor defaultConvert = new BeanProcessor();

  124.     /**
  125.      * The Singleton instance of this class.
  126.      */
  127.     private static final BasicRowProcessor instance = new BasicRowProcessor();

  128.     protected static Map<String, Object> createCaseInsensitiveHashMap(final int cols) {
  129.         return new CaseInsensitiveHashMap(cols);
  130.     }

  131.     /**
  132.      * Returns the Singleton instance of this class.
  133.      *
  134.      * @return The single instance of this class.
  135.      * @deprecated Create instances with the constructors instead.  This will
  136.      * be removed after DbUtils 1.1.
  137.      */
  138.     @Deprecated
  139.     public static BasicRowProcessor instance() {
  140.         return instance;
  141.     }

  142.     /**
  143.      * Use this to process beans.
  144.      */
  145.     private final BeanProcessor convert;

  146.     /**
  147.      * BasicRowProcessor constructor.  Bean processing defaults to a
  148.      * BeanProcessor instance.
  149.      */
  150.     public BasicRowProcessor() {
  151.         this(defaultConvert);
  152.     }

  153.     /**
  154.      * BasicRowProcessor constructor.
  155.      * @param convert The BeanProcessor to use when converting columns to
  156.      * bean properties.
  157.      * @since 1.1
  158.      */
  159.     public BasicRowProcessor(final BeanProcessor convert) {
  160.         this.convert = convert;
  161.     }

  162.     /**
  163.      * Convert a {@code ResultSet} row into an {@code Object[]}.
  164.      * This implementation copies column values into the array in the same
  165.      * order they're returned from the {@code ResultSet}.  Array elements
  166.      * will be set to {@code null} if the column was SQL NULL.
  167.      *
  168.      * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)
  169.      * @param resultSet ResultSet that supplies the array data
  170.      * @throws SQLException if a database access error occurs
  171.      * @return the newly created array
  172.      */
  173.     @Override
  174.     public Object[] toArray(final ResultSet resultSet) throws SQLException {
  175.         final ResultSetMetaData meta = resultSet.getMetaData();
  176.         final int cols = meta.getColumnCount();
  177.         final Object[] result = new Object[cols];

  178.         for (int i = 0; i < cols; i++) {
  179.             result[i] = resultSet.getObject(i + 1);
  180.         }

  181.         return result;
  182.     }

  183.     /**
  184.      * Convert a {@code ResultSet} row into a JavaBean.  This
  185.      * implementation delegates to a BeanProcessor instance.
  186.      * @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet, Class)
  187.      * @see org.apache.commons.dbutils.BeanProcessor#toBean(java.sql.ResultSet, Class)
  188.      * @param <T> The type of bean to create
  189.      * @param resultSet ResultSet that supplies the bean data
  190.      * @param type Class from which to create the bean instance
  191.      * @throws SQLException if a database access error occurs
  192.      * @return the newly created bean
  193.      */
  194.     @Override
  195.     public <T> T toBean(final ResultSet resultSet, final Class<? extends T> type) throws SQLException {
  196.         return this.convert.toBean(resultSet, type);
  197.     }

  198.     /**
  199.      * Convert a {@code ResultSet} into a {@code List} of JavaBeans.
  200.      * This implementation delegates to a BeanProcessor instance.
  201.      * @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet, Class)
  202.      * @see org.apache.commons.dbutils.BeanProcessor#toBeanList(java.sql.ResultSet, Class)
  203.      * @param <T> The type of bean to create
  204.      * @param resultSet ResultSet that supplies the bean data
  205.      * @param type Class from which to create the bean instance
  206.      * @throws SQLException if a database access error occurs
  207.      * @return A {@code List} of beans with the given type in the order
  208.      * they were returned by the {@code ResultSet}.
  209.      */
  210.     @Override
  211.     public <T> List<T> toBeanList(final ResultSet resultSet, final Class<? extends T> type) throws SQLException {
  212.         return this.convert.toBeanList(resultSet, type);
  213.     }


  214.     /**
  215.      * Convert a {@code ResultSet} row into a {@code Map}.
  216.      *
  217.      * <p>
  218.      * This implementation returns a {@code Map} with case insensitive column names as keys. Calls to
  219.      * {@code map.get("COL")} and {@code map.get("col")} return the same value. Furthermore this implementation
  220.      * will return an ordered map, that preserves the ordering of the columns in the ResultSet, so that iterating over
  221.      * the entry set of the returned map will return the first column of the ResultSet, then the second and so forth.
  222.      * </p>
  223.      *
  224.      * @param resultSet ResultSet that supplies the map data
  225.      * @return the newly created Map
  226.      * @throws SQLException if a database access error occurs
  227.      * @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet)
  228.      */
  229.     @Override
  230.     public Map<String, Object> toMap(final ResultSet resultSet) throws SQLException {
  231.         final ResultSetMetaData rsmd = resultSet.getMetaData();
  232.         final int cols = rsmd.getColumnCount();
  233.         final Map<String, Object> result = createCaseInsensitiveHashMap(cols);

  234.         for (int i = 1; i <= cols; i++) {
  235.             String propKey = rsmd.getColumnLabel(i);
  236.             if (null == propKey || 0 == propKey.length()) {
  237.               propKey = rsmd.getColumnName(i);
  238.             }
  239.             if (null == propKey || 0 == propKey.length()) {
  240.               // The column index can't be null
  241.               propKey = Integer.toString(i);
  242.             }
  243.             result.put(propKey, resultSet.getObject(i));
  244.         }

  245.         return result;
  246.     }

  247. }