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.List;
24 import java.util.Map;
25
26 /**
27 * Basic implementation of the <code>RowProcessor</code> 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 /**
38 * The default BeanProcessor instance to use if not supplied in the
39 * constructor.
40 */
41 private static final BeanProcessor defaultConvert = new BeanProcessor();
42
43 /**
44 * The Singleton instance of this class.
45 */
46 private static final BasicRowProcessor instance = new BasicRowProcessor();
47
48 /**
49 * Returns the Singleton instance of this class.
50 *
51 * @return The single instance of this class.
52 * @deprecated Create instances with the constructors instead. This will
53 * be removed after DbUtils 1.1.
54 */
55 public static BasicRowProcessor instance() {
56 return instance;
57 }
58
59 /**
60 * Use this to process beans.
61 */
62 private final BeanProcessor convert;
63
64 /**
65 * BasicRowProcessor constructor. Bean processing defaults to a
66 * BeanProcessor instance.
67 */
68 public BasicRowProcessor() {
69 this(defaultConvert);
70 }
71
72 /**
73 * BasicRowProcessor constructor.
74 * @param convert The BeanProcessor to use when converting columns to
75 * bean properties.
76 * @since DbUtils 1.1
77 */
78 public BasicRowProcessor(BeanProcessor convert) {
79 super();
80 this.convert = convert;
81 }
82
83 /**
84 * Convert a <code>ResultSet</code> row into an <code>Object[]</code>.
85 * This implementation copies column values into the array in the same
86 * order they're returned from the <code>ResultSet</code>. Array elements
87 * will be set to <code>null</code> if the column was SQL NULL.
88 *
89 * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)
90 * @param rs ResultSet that supplies the array data
91 * @throws SQLException if a database access error occurs
92 * @return the newly created array
93 */
94 public Object[] toArray(ResultSet rs) throws SQLException {
95 ResultSetMetaData meta = rs.getMetaData();
96 int cols = meta.getColumnCount();
97 Object[] result = new Object[cols];
98
99 for (int i = 0; i < cols; i++) {
100 result[i] = rs.getObject(i + 1);
101 }
102
103 return result;
104 }
105
106 /**
107 * Convert a <code>ResultSet</code> row into a JavaBean. This
108 * implementation delegates to a BeanProcessor instance.
109 * @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet, java.lang.Class)
110 * @see org.apache.commons.dbutils.BeanProcessor#toBean(java.sql.ResultSet, java.lang.Class)
111 * @param <T> The type of bean to create
112 * @param rs ResultSet that supplies the bean data
113 * @param type Class from which to create the bean instance
114 * @throws SQLException if a database access error occurs
115 * @return the newly created bean
116 */
117 public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
118 return this.convert.toBean(rs, type);
119 }
120
121 /**
122 * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans.
123 * This implementation delegates to a BeanProcessor instance.
124 * @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet, java.lang.Class)
125 * @see org.apache.commons.dbutils.BeanProcessor#toBeanList(java.sql.ResultSet, java.lang.Class)
126 * @param <T> The type of bean to create
127 * @param rs ResultSet that supplies the bean data
128 * @param type Class from which to create the bean instance
129 * @throws SQLException if a database access error occurs
130 * @return A <code>List</code> of beans with the given type in the order
131 * they were returned by the <code>ResultSet</code>.
132 */
133 public <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException {
134 return this.convert.toBeanList(rs, type);
135 }
136
137 /**
138 * Convert a <code>ResultSet</code> row into a <code>Map</code>. This
139 * implementation returns a <code>Map</code> with case insensitive column
140 * names as keys. Calls to <code>map.get("COL")</code> and
141 * <code>map.get("col")</code> return the same value.
142 * @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet)
143 * @param rs ResultSet that supplies the map data
144 * @throws SQLException if a database access error occurs
145 * @return the newly created Map
146 */
147 public Map<String, Object> toMap(ResultSet rs) throws SQLException {
148 Map<String, Object> result = new CaseInsensitiveHashMap();
149 ResultSetMetaData rsmd = rs.getMetaData();
150 int cols = rsmd.getColumnCount();
151
152 for (int i = 1; i <= cols; i++) {
153 result.put(rsmd.getColumnName(i), rs.getObject(i));
154 }
155
156 return result;
157 }
158
159 /**
160 * A Map that converts all keys to lowercase Strings for case insensitive
161 * lookups. This is needed for the toMap() implementation because
162 * databases don't consistenly handle the casing of column names.
163 *
164 * <p>The keys are stored as they are given [BUG #DBUTILS-34], so we maintain
165 * an internal mapping from lowercase keys to the real keys in order to
166 * achieve the case insensitive lookup.
167 *
168 * <p>Note: This implementation does not allow <tt>null</tt>
169 * for key, whereas {@link HashMap} does, because of the code:
170 * <pre>
171 * key.toString().toLowerCase()
172 * </pre>
173 */
174 private static class CaseInsensitiveHashMap extends HashMap<String, Object> {
175 /**
176 * The internal mapping from lowercase keys to the real keys.
177 *
178 * <p>
179 * Any query operation using the key
180 * ({@link #get(Object)}, {@link #containsKey(Object)})
181 * is done in three steps:
182 * <ul>
183 * <li>convert the parameter key to lower case</li>
184 * <li>get the actual key that corresponds to the lower case key</li>
185 * <li>query the map with the actual key</li>
186 * </ul>
187 * </p>
188 */
189 private final Map<String,String> lowerCaseMap = new HashMap<String,String>();
190
191 /**
192 * Required for serialization support.
193 *
194 * @see java.io.Serializable
195 */
196 private static final long serialVersionUID = -2848100435296897392L;
197
198 /** {@inheritDoc} */
199 @Override
200 public boolean containsKey(Object key) {
201 Object realKey = lowerCaseMap.get(key.toString().toLowerCase());
202 return super.containsKey(realKey);
203 // Possible optimisation here:
204 // Since the lowerCaseMap contains a mapping for all the keys,
205 // we could just do this:
206 // return lowerCaseMap.containsKey(key.toString().toLowerCase());
207 }
208
209 /** {@inheritDoc} */
210 @Override
211 public Object get(Object key) {
212 Object realKey = lowerCaseMap.get(key.toString().toLowerCase());
213 return super.get(realKey);
214 }
215
216 /** {@inheritDoc} */
217 @Override
218 public Object put(String key, Object value) {
219 /*
220 * In order to keep the map and lowerCaseMap synchronized,
221 * we have to remove the old mapping before putting the
222 * new one. Indeed, oldKey and key are not necessaliry equals.
223 * (That's why we call super.remove(oldKey) and not just
224 * super.put(key, value))
225 */
226 Object oldKey = lowerCaseMap.put(key.toLowerCase(), key);
227 Object oldValue = super.remove(oldKey);
228 super.put(key, value);
229 return oldValue;
230 }
231
232 /** {@inheritDoc} */
233 @Override
234 public void putAll(Map<? extends String,?> m) {
235 for (Map.Entry<? extends String, ?> entry : m.entrySet()) {
236 String key = entry.getKey();
237 Object value = entry.getValue();
238 this.put(key, value);
239 }
240 }
241
242 /** {@inheritDoc} */
243 @Override
244 public Object remove(Object key) {
245 Object realKey = lowerCaseMap.remove(key.toString().toLowerCase());
246 return super.remove(realKey);
247 }
248 }
249
250 }