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.lang.reflect.InvocationHandler;
20  import java.lang.reflect.Method;
21  import java.sql.ResultSet;
22  import java.sql.ResultSetMetaData;
23  import java.sql.SQLException;
24  import java.util.Arrays;
25  import java.util.Collections;
26  import java.util.Iterator;
27  import java.util.List;
28  
29  /**
30   * MockResultSet dynamically implements the ResultSet interface.
31   */
32  public class MockResultSet implements InvocationHandler {
33  
34      /**
35       * Create a {@code MockResultSet} proxy object.  This is equivalent to:
36       * <pre>
37       * ProxyFactory.instance().createResultSet(new MockResultSet(metaData, rows));
38       * </pre>
39       *
40       * @param metaData
41       * @param rows A null value indicates an empty {@code ResultSet}.
42       */
43      public static ResultSet create(final ResultSetMetaData metaData,
44              final Object[][] rows) {
45          return ProxyFactory.instance().createResultSet(
46              new MockResultSet(metaData, rows));
47      }
48  
49      private Object[] currentRow = null;
50  
51      private Iterator<Object[]> iter = null;
52  
53      private ResultSetMetaData metaData = null;
54  
55      private Boolean wasNull = Boolean.FALSE;
56  
57      /**
58       * MockResultSet constructor.
59       * @param metaData
60       * @param rows A null value indicates an empty {@code ResultSet}.
61       */
62      public MockResultSet(final ResultSetMetaData metaData, final Object[][] rows) {
63          this.metaData = metaData;
64          if (rows == null) {
65              final List<Object[]> empty = Collections.emptyList();
66              this.iter = empty.iterator();
67          } else {
68              this.iter = Arrays.asList(rows).iterator();
69          }
70      }
71  
72      /**
73       * The get* methods can have an int column index or a String column name as
74       * the parameter.  This method handles both cases and returns the column
75       * index that the client is trying to get at.
76       * @param args
77       * @return A column index.
78       * @throws SQLException if a database access error occurs
79       */
80      private int columnIndex(final Object[] args) throws SQLException {
81  
82          if (args[0] instanceof Integer) {
83              return ((Integer) args[0]).intValue();
84  
85          }
86          if (args[0] instanceof String) {
87              return this.columnNameToIndex((String) args[0]);
88  
89          }
90          throw new SQLException(args[0] + " must be Integer or String");
91      }
92  
93      /**
94       * Returns the column index for the given column name.
95       * @return A 1 based index
96       * @throws SQLException if the column name is invalid
97       */
98      private int columnNameToIndex(final String columnName) throws SQLException {
99          for (int i = 0; i < this.currentRow.length; i++) {
100             final int c = i + 1;
101             if (this.metaData.getColumnName(c).equalsIgnoreCase(columnName)) {
102                 return c;
103             }
104         }
105 
106         throw new SQLException(columnName + " is not a valid column name.");
107     }
108 
109     /**
110      * Gets the boolean value at the given column index.
111      * @param columnIndex A 1 based index.
112      * @throws SQLException if a database access error occurs
113      */
114     protected Object getBoolean(final int columnIndex) throws SQLException {
115         final Object obj = this.currentRow[columnIndex - 1];
116         this.setWasNull(obj);
117 
118         try {
119             return obj == null
120                 ? Boolean.FALSE
121                 : Boolean.valueOf(obj.toString());
122 
123         } catch (final NumberFormatException e) {
124             throw new SQLException(e.getMessage());
125         }
126     }
127 
128     /**
129      * Gets the byte value at the given column index.
130      * @param columnIndex A 1 based index.
131      * @throws SQLException if a database access error occurs
132      */
133     protected Object getByte(final int columnIndex) throws SQLException {
134         final Object obj = this.currentRow[columnIndex - 1];
135         this.setWasNull(obj);
136 
137         try {
138             return obj == null
139                 ? Byte.valueOf((byte) 0)
140                 : Byte.valueOf(obj.toString());
141 
142         } catch (final NumberFormatException e) {
143             throw new SQLException(e.getMessage());
144         }
145     }
146 
147     /**
148      * Gets the double value at the given column index.
149      * @param columnIndex A 1 based index.
150      * @throws SQLException if a database access error occurs
151      */
152     protected Object getDouble(final int columnIndex) throws SQLException {
153         final Object obj = this.currentRow[columnIndex - 1];
154         this.setWasNull(obj);
155 
156         try {
157             return obj == null
158                 ? Double.valueOf(0)
159                 : Double.valueOf(obj.toString());
160 
161         } catch (final NumberFormatException e) {
162             throw new SQLException(e.getMessage());
163         }
164     }
165 
166     /**
167      * Gets the float value at the given column index.
168      * @param columnIndex A 1 based index.
169      * @throws SQLException if a database access error occurs
170      */
171     protected Object getFloat(final int columnIndex) throws SQLException {
172         final Object obj = this.currentRow[columnIndex - 1];
173         this.setWasNull(obj);
174 
175         try {
176             return obj == null ? Float.valueOf(0) : Float.valueOf(obj.toString());
177 
178         } catch (final NumberFormatException e) {
179             throw new SQLException(e.getMessage());
180         }
181     }
182 
183     /**
184      * Gets the int value at the given column index.
185      * @param columnIndex A 1 based index.
186      * @throws SQLException if a database access error occurs
187      */
188     protected Object getInt(final int columnIndex) throws SQLException {
189         final Object obj = this.currentRow[columnIndex - 1];
190         this.setWasNull(obj);
191 
192         try {
193             return obj == null
194                 ? Integer.valueOf(0)
195                 : Integer.valueOf(obj.toString());
196 
197         } catch (final NumberFormatException e) {
198             throw new SQLException(e.getMessage());
199         }
200     }
201 
202     /**
203      * Gets the long value at the given column index.
204      * @param columnIndex A 1 based index.
205      * @throws SQLException if a database access error occurs
206      */
207     protected Object getLong(final int columnIndex) throws SQLException {
208         final Object obj = this.currentRow[columnIndex - 1];
209         this.setWasNull(obj);
210 
211         try {
212             return obj == null ? Long.valueOf(0) : Long.valueOf(obj.toString());
213 
214         } catch (final NumberFormatException e) {
215             throw new SQLException(e.getMessage());
216         }
217     }
218 
219     /**
220      * @throws SQLException
221      */
222     protected ResultSetMetaData getMetaData() throws SQLException {
223         return this.metaData;
224     }
225 
226     /**
227      * Gets the object at the given column index.
228      * @param columnIndex A 1 based index.
229      * @throws SQLException if a database access error occurs
230      */
231     protected Object getObject(final int columnIndex) throws SQLException {
232         final Object obj = this.currentRow[columnIndex - 1];
233         this.setWasNull(obj);
234         return obj;
235     }
236 
237     /**
238      * Gets the short value at the given column index.
239      * @param columnIndex A 1 based index.
240      * @throws SQLException if a database access error occurs
241      */
242     protected Object getShort(final int columnIndex) throws SQLException {
243         final Object obj = this.currentRow[columnIndex - 1];
244         this.setWasNull(obj);
245 
246         try {
247             return obj == null
248                 ? Short.valueOf((short) 0)
249                 : Short.valueOf(obj.toString());
250 
251         } catch (final NumberFormatException e) {
252             throw new SQLException(e.getMessage());
253         }
254     }
255 
256     /**
257      * Gets the String at the given column index.
258      * @param columnIndex A 1 based index.
259      * @throws SQLException if a database access error occurs
260      */
261     protected String getString(final int columnIndex) throws SQLException {
262         final Object obj = this.getObject(columnIndex);
263         this.setWasNull(obj);
264         return obj == null ? null : obj.toString();
265     }
266 
267     @Override
268     public Object invoke(final Object proxy, final Method method, final Object[] args)
269         throws Throwable {
270 
271         final String methodName = method.getName();
272 
273         if (methodName.equals("getMetaData")) {
274             return this.getMetaData();
275 
276         }
277         if (methodName.equals("next")) {
278             return this.next();
279 
280         }
281         if (methodName.equals("previous")) {
282 
283         } else if (methodName.equals("close")) {
284 
285         } else if (methodName.equals("getBoolean")) {
286             return this.getBoolean(columnIndex(args));
287 
288         } else if (methodName.equals("getByte")) {
289             return this.getByte(columnIndex(args));
290 
291         } else if (methodName.equals("getDouble")) {
292             return this.getDouble(columnIndex(args));
293 
294         } else if (methodName.equals("getFloat")) {
295             return this.getFloat(columnIndex(args));
296 
297         } else if (methodName.equals("getInt")) {
298             return this.getInt(columnIndex(args));
299 
300         } else if (methodName.equals("getLong")) {
301             return this.getLong(columnIndex(args));
302 
303         } else if (methodName.equals("getObject")) {
304             return this.getObject(columnIndex(args));
305 
306         } else if (methodName.equals("getShort")) {
307             return this.getShort(columnIndex(args));
308 
309         } else if (methodName.equals("getString")) {
310             return this.getString(columnIndex(args));
311 
312         } else if (methodName.equals("wasNull")) {
313             return this.wasNull();
314 
315         } else if (methodName.equals("isLast")) {
316             return this.isLast();
317 
318         } else if (methodName.equals("hashCode")) {
319             return Integer.valueOf(System.identityHashCode(proxy));
320 
321         } else if (methodName.equals("toString")) {
322             return "MockResultSet " + System.identityHashCode(proxy);
323 
324         } else if (methodName.equals("equals")) {
325             return Boolean.valueOf(proxy == args[0]);
326         }
327 
328         throw new UnsupportedOperationException("Unsupported method: " + methodName);
329     }
330 
331     /**
332      * @throws SQLException
333      */
334     protected Boolean isLast() throws SQLException {
335         return this.iter.hasNext() ? Boolean.FALSE : Boolean.TRUE;
336     }
337 
338     /**
339      * @throws SQLException
340      */
341     protected Boolean next() throws SQLException {
342         if (!this.iter.hasNext()) {
343             return Boolean.FALSE;
344         }
345         this.currentRow = iter.next();
346         return Boolean.TRUE;
347     }
348 
349     /**
350      * Assigns this.wasNull a Boolean value based on the object passed in.
351      * @param isNull
352      */
353     private void setWasNull(final Object isNull) {
354         this.wasNull = isNull == null ? Boolean.TRUE : Boolean.FALSE;
355     }
356 
357     /**
358      * @throws SQLException
359      */
360     protected Boolean wasNull() throws SQLException {
361         return this.wasNull;
362     }
363 }