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 public static BasicRowProcessor instance() {
056 return instance;
057 }
058
059 /**
060 * Use this to process beans.
061 */
062 private final BeanProcessor convert;
063
064 /**
065 * BasicRowProcessor constructor. Bean processing defaults to a
066 * BeanProcessor instance.
067 */
068 public BasicRowProcessor() {
069 this(defaultConvert);
070 }
071
072 /**
073 * BasicRowProcessor constructor.
074 * @param convert The BeanProcessor to use when converting columns to
075 * bean properties.
076 * @since DbUtils 1.1
077 */
078 public BasicRowProcessor(BeanProcessor convert) {
079 super();
080 this.convert = convert;
081 }
082
083 /**
084 * Convert a <code>ResultSet</code> row into an <code>Object[]</code>.
085 * This implementation copies column values into the array in the same
086 * order they're returned from the <code>ResultSet</code>. Array elements
087 * will be set to <code>null</code> if the column was SQL NULL.
088 *
089 * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)
090 * @param rs ResultSet that supplies the array data
091 * @throws SQLException if a database access error occurs
092 * @return the newly created array
093 */
094 public Object[] toArray(ResultSet rs) throws SQLException {
095 ResultSetMetaData meta = rs.getMetaData();
096 int cols = meta.getColumnCount();
097 Object[] result = new Object[cols];
098
099 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 }