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 }