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 }