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