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