View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.dbutils;
18  
19  import java.sql.ResultSet;
20  import java.sql.ResultSetMetaData;
21  import java.sql.SQLException;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  
27  /**
28   * Basic implementation of the <code>RowProcessor</code> interface.
29   * 
30   * <p>
31   * This class is thread-safe.
32   * </p>
33   * 
34   * @see RowProcessor
35   */
36  public class BasicRowProcessor implements RowProcessor {
37  
38      /**
39       * The default BeanProcessor instance to use if not supplied in the
40       * constructor.
41       */
42      private static final BeanProcessor defaultConvert = new BeanProcessor();
43  
44      /**
45       * The Singleton instance of this class.
46       */
47      private static final BasicRowProcessor instance = new BasicRowProcessor();
48  
49      /**
50       * Returns the Singleton instance of this class.
51       *
52       * @return The single instance of this class.
53       * @deprecated Create instances with the constructors instead.  This will 
54       * be removed after DbUtils 1.1.
55       */
56      public static BasicRowProcessor instance() {
57          return instance;
58      }
59  
60      /**
61       * Use this to process beans.
62       */
63      private final BeanProcessor convert;
64  
65      /**
66       * BasicRowProcessor constructor.  Bean processing defaults to a 
67       * BeanProcessor instance.
68       */
69      public BasicRowProcessor() {
70          this(defaultConvert);
71      }
72      
73      /**
74       * BasicRowProcessor constructor.
75       * @param convert The BeanProcessor to use when converting columns to 
76       * bean properties.
77       * @since DbUtils 1.1
78       */
79      public BasicRowProcessor(BeanProcessor convert) {
80          super();
81          this.convert = convert;
82      }
83  
84      /**
85       * Convert a <code>ResultSet</code> row into an <code>Object[]</code>.
86       * This implementation copies column values into the array in the same 
87       * order they're returned from the <code>ResultSet</code>.  Array elements
88       * will be set to <code>null</code> if the column was SQL NULL.
89       *
90       * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)
91       */
92      public Object[] toArray(ResultSet rs) throws SQLException {
93          ResultSetMetaData meta = rs.getMetaData();
94          int cols = meta.getColumnCount();
95          Object[] result = new Object[cols];
96  
97          for (int i = 0; i < cols; i++) {
98              result[i] = rs.getObject(i + 1);
99          }
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 }