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 }