/* | |
* Licensed to the Apache Software Foundation (ASF) under one or more | |
* contributor license agreements. See the NOTICE file distributed with | |
* this work for additional information regarding copyright ownership. | |
* The ASF licenses this file to You under the Apache License, Version 2.0 | |
* (the "License"); you may not use this file except in compliance with | |
* the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.apache.commons.dbutils; | |
import java.sql.ResultSet; | |
import java.sql.ResultSetMetaData; | |
import java.sql.SQLException; | |
import java.util.HashMap; | |
import java.util.LinkedHashMap; | |
import java.util.List; | |
import java.util.Locale; | |
import java.util.Map; | |
/** | |
* Basic implementation of the {@code RowProcessor} interface. | |
* | |
* <p> | |
* This class is thread-safe. | |
* </p> | |
* | |
* @see RowProcessor | |
*/ | |
public class BasicRowProcessor implements RowProcessor { | |
/** | |
* A Map that converts all keys to lowercase Strings for case insensitive | |
* lookups. This is needed for the toMap() implementation because | |
* databases don't consistently handle the casing of column names. | |
* | |
* <p>The keys are stored as they are given [BUG #DBUTILS-34], so we maintain | |
* an internal mapping from lowercase keys to the real keys in order to | |
* achieve the case insensitive lookup. | |
* | |
* <p>Note: This implementation does not allow {@code null} | |
* for key, whereas {@link LinkedHashMap} does, because of the code: | |
* <pre> | |
* key.toString().toLowerCase() | |
* </pre> | |
*/ | |
private static final class CaseInsensitiveHashMap extends LinkedHashMap<String, Object> { | |
/** | |
* Required for serialization support. | |
* | |
* @see java.io.Serializable | |
*/ | |
private static final long serialVersionUID = -2848100435296897392L; | |
/** | |
* The internal mapping from lowercase keys to the real keys. | |
* | |
* <p> | |
* Any query operation using the key | |
* ({@link #get(Object)}, {@link #containsKey(Object)}) | |
* is done in three steps: | |
* <ul> | |
* <li>convert the parameter key to lower case</li> | |
* <li>get the actual key that corresponds to the lower case key</li> | |
* <li>query the map with the actual key</li> | |
* </ul> | |
* </p> | |
*/ | |
private final Map<String, String> lowerCaseMap = new HashMap<>(); | |
private CaseInsensitiveHashMap(final int initialCapacity) { | |
super(initialCapacity); | |
} | |
/** {@inheritDoc} */ | |
@Override | |
public boolean containsKey(final Object key) { | |
final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH)); | |
return super.containsKey(realKey); | |
// Possible optimization here: | |
// Since the lowerCaseMap contains a mapping for all the keys, | |
// we could just do this: | |
// return lowerCaseMap.containsKey(key.toString().toLowerCase()); | |
} | |
/** {@inheritDoc} */ | |
@Override | |
public Object get(final Object key) { | |
final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH)); | |
return super.get(realKey); | |
} | |
/** {@inheritDoc} */ | |
@Override | |
public Object put(final String key, final Object value) { | |
/* | |
* In order to keep the map and lowerCaseMap synchronized, | |
* we have to remove the old mapping before putting the | |
* new one. Indeed, oldKey and key are not necessarily equals. | |
* (That's why we call super.remove(oldKey) and not just | |
* super.put(key, value)) | |
*/ | |
final Object oldKey = lowerCaseMap.put(key.toLowerCase(Locale.ENGLISH), key); | |
final Object oldValue = super.remove(oldKey); | |
super.put(key, value); | |
return oldValue; | |
} | |
/** {@inheritDoc} */ | |
@Override | |
public void putAll(final Map<? extends String, ?> m) { | |
m.forEach(this::put); | |
} | |
/** {@inheritDoc} */ | |
@Override | |
public Object remove(final Object key) { | |
final Object realKey = lowerCaseMap.remove(key.toString().toLowerCase(Locale.ENGLISH)); | |
return super.remove(realKey); | |
} | |
} | |
/** | |
* The default BeanProcessor instance to use if not supplied in the | |
* constructor. | |
*/ | |
private static final BeanProcessor defaultConvert = new BeanProcessor(); | |
/** | |
* The Singleton instance of this class. | |
*/ | |
private static final BasicRowProcessor instance = new BasicRowProcessor(); | |
protected static Map<String, Object> createCaseInsensitiveHashMap(final int cols) { | |
return new CaseInsensitiveHashMap(cols); | |
} | |
/** | |
* Returns the Singleton instance of this class. | |
* | |
* @return The single instance of this class. | |
* @deprecated Create instances with the constructors instead. This will | |
* be removed after DbUtils 1.1. | |
*/ | |
@Deprecated | |
public static BasicRowProcessor instance() { | |
return instance; | |
} | |
/** | |
* Use this to process beans. | |
*/ | |
private final BeanProcessor convert; | |
/** | |
* BasicRowProcessor constructor. Bean processing defaults to a | |
* BeanProcessor instance. | |
*/ | |
public BasicRowProcessor() { | |
this(defaultConvert); | |
} | |
/** | |
* BasicRowProcessor constructor. | |
* @param convert The BeanProcessor to use when converting columns to | |
* bean properties. | |
* @since 1.1 | |
*/ | |
public BasicRowProcessor(final BeanProcessor convert) { | |
this.convert = convert; | |
} | |
/** | |
* Convert a {@code ResultSet} row into an {@code Object[]}. | |
* This implementation copies column values into the array in the same | |
* order they're returned from the {@code ResultSet}. Array elements | |
* will be set to {@code null} if the column was SQL NULL. | |
* | |
* @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet) | |
* @param resultSet ResultSet that supplies the array data | |
* @throws SQLException if a database access error occurs | |
* @return the newly created array | |
*/ | |
@Override | |
public Object[] toArray(final ResultSet resultSet) throws SQLException { | |
final ResultSetMetaData meta = resultSet.getMetaData(); | |
final int cols = meta.getColumnCount(); | |
final Object[] result = new Object[cols]; | |
for (int i = 0; i < cols; i++) { | |
result[i] = resultSet.getObject(i + 1); | |
} | |
return result; | |
} | |
/** | |
* Convert a {@code ResultSet} row into a JavaBean. This | |
* implementation delegates to a BeanProcessor instance. | |
* @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet, Class) | |
* @see org.apache.commons.dbutils.BeanProcessor#toBean(java.sql.ResultSet, Class) | |
* @param <T> The type of bean to create | |
* @param resultSet ResultSet that supplies the bean data | |
* @param type Class from which to create the bean instance | |
* @throws SQLException if a database access error occurs | |
* @return the newly created bean | |
*/ | |
@Override | |
public <T> T toBean(final ResultSet resultSet, final Class<? extends T> type) throws SQLException { | |
return this.convert.toBean(resultSet, type); | |
} | |
/** | |
* Convert a {@code ResultSet} into a {@code List} of JavaBeans. | |
* This implementation delegates to a BeanProcessor instance. | |
* @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet, Class) | |
* @see org.apache.commons.dbutils.BeanProcessor#toBeanList(java.sql.ResultSet, Class) | |
* @param <T> The type of bean to create | |
* @param resultSet ResultSet that supplies the bean data | |
* @param type Class from which to create the bean instance | |
* @throws SQLException if a database access error occurs | |
* @return A {@code List} of beans with the given type in the order | |
* they were returned by the {@code ResultSet}. | |
*/ | |
@Override | |
public <T> List<T> toBeanList(final ResultSet resultSet, final Class<? extends T> type) throws SQLException { | |
return this.convert.toBeanList(resultSet, type); | |
} | |
/** | |
* Convert a {@code ResultSet} row into a {@code Map}. | |
* | |
* <p> | |
* This implementation returns a {@code Map} with case insensitive column names as keys. Calls to | |
* {@code map.get("COL")} and {@code map.get("col")} return the same value. Furthermore this implementation | |
* will return an ordered map, that preserves the ordering of the columns in the ResultSet, so that iterating over | |
* the entry set of the returned map will return the first column of the ResultSet, then the second and so forth. | |
* </p> | |
* | |
* @param resultSet ResultSet that supplies the map data | |
* @return the newly created Map | |
* @throws SQLException if a database access error occurs | |
* @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet) | |
*/ | |
@Override | |
public Map<String, Object> toMap(final ResultSet resultSet) throws SQLException { | |
final ResultSetMetaData rsmd = resultSet.getMetaData(); | |
final int cols = rsmd.getColumnCount(); | |
final Map<String, Object> result = createCaseInsensitiveHashMap(cols); | |
for (int i = 1; i <= cols; i++) { | |
String propKey = rsmd.getColumnLabel(i); | |
if (null == propKey || 0 == propKey.length()) { | |
propKey = rsmd.getColumnName(i); | |
} | |
if (null == propKey || 0 == propKey.length()) { | |
// The column index can't be null | |
propKey = Integer.toString(i); | |
} | |
result.put(propKey, resultSet.getObject(i)); | |
} | |
return result; | |
} | |
} |