View Javadoc
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  
19  import java.sql.ResultSet;
20  import java.sql.ResultSetMetaData;
21  import java.sql.SQLException;
22  import java.util.HashMap;
23  import java.util.LinkedHashMap;
24  import java.util.List;
25  import java.util.Locale;
26  import java.util.Map;
27  
28  /**
29   * Basic implementation of the {@code RowProcessor} interface.
30   *
31   * <p>
32   * This class is thread-safe.
33   * </p>
34   *
35   * @see RowProcessor
36   */
37  public class BasicRowProcessor implements RowProcessor {
38  
39      /**
40       * A Map that converts all keys to lowercase Strings for case insensitive
41       * lookups.  This is needed for the toMap() implementation because
42       * databases don't consistently handle the casing of column names.
43       *
44       * <p>The keys are stored as they are given [BUG #DBUTILS-34], so we maintain
45       * an internal mapping from lowercase keys to the real keys in order to
46       * achieve the case insensitive lookup.
47       *
48       * <p>Note: This implementation does not allow {@code null}
49       * for key, whereas {@link LinkedHashMap} does, because of the code:
50       * <pre>
51       * key.toString().toLowerCase()
52       * </pre>
53       */
54      private static final class CaseInsensitiveHashMap extends LinkedHashMap<String, Object> {
55  
56          /**
57           * Required for serialization support.
58           *
59           * @see java.io.Serializable
60           */
61          private static final long serialVersionUID = -2848100435296897392L;
62  
63          /**
64           * The internal mapping from lowercase keys to the real keys.
65           *
66           * <p>
67           * Any query operation using the key
68           * ({@link #get(Object)}, {@link #containsKey(Object)})
69           * is done in three steps:
70           * <ul>
71           * <li>convert the parameter key to lower case</li>
72           * <li>get the actual key that corresponds to the lower case key</li>
73           * <li>query the map with the actual key</li>
74           * </ul>
75           * </p>
76           */
77          private final Map<String, String> lowerCaseMap = new HashMap<>();
78  
79          private CaseInsensitiveHashMap(final int initialCapacity) {
80              super(initialCapacity);
81          }
82  
83          /** {@inheritDoc} */
84          @Override
85          public boolean containsKey(final Object key) {
86              final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
87              return super.containsKey(realKey);
88              // Possible optimisation here:
89              // Since the lowerCaseMap contains a mapping for all the keys,
90              // we could just do this:
91              // return lowerCaseMap.containsKey(key.toString().toLowerCase());
92          }
93  
94          /** {@inheritDoc} */
95          @Override
96          public Object get(final Object key) {
97              final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
98              return super.get(realKey);
99          }
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 }