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