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