001 package com.mockrunner.jdbc;
002
003 import java.util.ArrayList;
004 import java.util.Collections;
005 import java.util.HashMap;
006 import java.util.Iterator;
007 import java.util.List;
008 import java.util.Map;
009
010 import com.mockrunner.mock.jdbc.MockResultSet;
011 import com.mockrunner.util.common.ArrayUtil;
012
013 /**
014 * Abstract base class for all statement types
015 * that support parameters, i.e. <code>PreparedStatement</code>
016 * and <code>CallableStatement</code>.
017 */
018 public abstract class AbstractParameterResultSetHandler extends AbstractResultSetHandler
019 {
020 private boolean exactMatchParameter = false;
021 private Map resultSetsForStatement = new HashMap();
022 private Map updateCountForStatement = new HashMap();
023 private Map throwsSQLException = new HashMap();
024 private Map executedStatementParameters = new HashMap();
025
026 /**
027 * Collects all SQL strings that were executed.
028 * @param sql the SQL string
029 * @param parameters a copy of the corresponding parameter map
030 */
031 public void addParameterMapForExecutedStatement(String sql, Map parameters)
032 {
033 if(null != parameters)
034 {
035 if(null == executedStatementParameters.get(sql))
036 {
037 executedStatementParameters.put(sql, new ParameterSets(sql));
038 }
039 ParameterSets sets = (ParameterSets)executedStatementParameters.get(sql);
040 sets.addParameterSet(parameters);
041 }
042 }
043
044 /**
045 * Returns the <code>ParameterSets</code> for a specified
046 * SQL string.
047 * @param sql the SQL string
048 * @return the <code>Map</code> of parameters
049 */
050 public ParameterSets getParametersForExecutedStatement(String sql)
051 {
052 return (ParameterSets)executedStatementParameters.get(sql);
053 }
054
055 /**
056 * Returns the <code>Map</code> of executed SQL strings.
057 * Each string maps to the corresponding {@link ParameterSets}
058 * object.
059 * @return the <code>Map</code> of parameters
060 */
061 public Map getExecutedStatementParameter()
062 {
063 return Collections.unmodifiableMap(executedStatementParameters);
064 }
065
066 /**
067 * Sets if the specified parameters must match exactly
068 * in order and number.
069 * Defaults to <code>false</code>, i.e. the specified
070 * parameters must be present in the actual parameter
071 * list of the prepared statement with the correct index
072 * but it's ok if there are more actual parameters.
073 * @param exactMatchParameter must parameters match exactly
074 */
075 public void setExactMatchParameter(boolean exactMatchParameter)
076 {
077 this.exactMatchParameter = exactMatchParameter;
078 }
079
080 /**
081 * Returns the first update count that matches the
082 * specified SQL string and the specified parameters.
083 * Please note that you can modify the match parameters with
084 * {@link #setCaseSensitive}, {@link #setExactMatch} and
085 * {@link #setUseRegularExpressions} and the match parameters for the
086 * specified parameter list with {@link #setExactMatchParameter}.
087 * @param sql the SQL string
088 * @param parameters the parameters
089 * @return the corresponding update count
090 */
091 public Integer getUpdateCount(String sql, Map parameters)
092 {
093 SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions());
094 List list = matcher.getMatchingObjects(updateCountForStatement, sql, true, true);
095 for(int ii = 0; ii < list.size(); ii++)
096 {
097 MockUpdateCountWrapper wrapper = (MockUpdateCountWrapper)list.get(ii);
098 if(doParameterMatch(wrapper.getParamters(), parameters))
099 {
100 return wrapper.getUpdateCount();
101 }
102 }
103 return null;
104 }
105
106 /**
107 * Returns the first <code>ResultSet</code> that matches the
108 * specified SQL string and the specified parameters.
109 * Please note that you can modify the match parameters with
110 * {@link #setCaseSensitive}, {@link #setExactMatch} and
111 * {@link #setUseRegularExpressions} and the match parameters for the
112 * specified parameter list with {@link #setExactMatchParameter}.
113 * @param sql the SQL string
114 * @param parameters the parameters
115 * @return the corresponding {@link MockResultSet}
116 */
117 public MockResultSet getResultSet(String sql, Map parameters)
118 {
119 SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions());
120 List list = matcher.getMatchingObjects(resultSetsForStatement, sql, true, true);
121 for(int ii = 0; ii < list.size(); ii++)
122 {
123 MockResultSetWrapper wrapper = (MockResultSetWrapper)list.get(ii);
124 if(doParameterMatch(wrapper.getParamters(), parameters))
125 {
126 return wrapper.getResultSet();
127 }
128 }
129 return null;
130 }
131
132 /**
133 * Returns if the specified SQL string with the specified parameters
134 * should raise an exception.
135 * This can be used to simulate database exceptions.
136 * Please note that you can modify the match parameters with
137 * {@link #setCaseSensitive}, {@link #setExactMatch} and
138 * {@link #setUseRegularExpressions} and the match parameters for the
139 * specified parameter list with {@link #setExactMatchParameter}.
140 * @param sql the SQL string
141 * @param parameters the parameters
142 * @return <code>true</code> if the specified SQL string should raise an exception,
143 * <code>false</code> otherwise
144 */
145 public boolean getThrowsSQLException(String sql, Map parameters)
146 {
147 SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions());
148 List list = matcher.getMatchingObjects(throwsSQLException, sql, true, true);
149 for(int ii = 0; ii < list.size(); ii++)
150 {
151 if(doParameterMatch((Map)list.get(ii), parameters))
152 {
153 return true;
154 }
155 }
156 return false;
157 }
158
159 protected boolean doParameterMatch(Map expectedParameters, Map actualParameters)
160 {
161 if(exactMatchParameter)
162 {
163 if(actualParameters.size() != expectedParameters.size()) return false;
164 Iterator iterator = actualParameters.keySet().iterator();
165 while(iterator.hasNext())
166 {
167 Object currentKey = iterator.next();
168 Object expectedObject = expectedParameters.get(currentKey);
169 if(null == expectedObject) return false;
170 if(!ParameterUtil.compareParameter(actualParameters.get(currentKey), expectedObject))
171 {
172 return false;
173 }
174 }
175 return true;
176 }
177 else
178 {
179 Iterator iterator = expectedParameters.keySet().iterator();
180 while(iterator.hasNext())
181 {
182 Object currentKey = iterator.next();
183 Object actualObject = actualParameters.get(currentKey);
184 if(null == actualObject) return false;
185 if(!ParameterUtil.compareParameter(actualObject, expectedParameters.get(currentKey)))
186 {
187 return false;
188 }
189 }
190 return true;
191 }
192 }
193
194 /**
195 * Clears the <code>ResultSet</code> objects.
196 */
197 public void clearResultSets()
198 {
199 super.clearResultSets();
200 resultSetsForStatement.clear();
201 }
202
203 /**
204 * Clears the update counts.
205 */
206 public void clearUpdateCounts()
207 {
208 super.clearUpdateCounts();
209 updateCountForStatement.clear();
210 }
211
212 /**
213 * Clears the list of statements that should throw an exception
214 */
215 public void clearThrowsSQLException()
216 {
217 super.clearThrowsSQLException();
218 throwsSQLException.clear();
219 }
220
221 /**
222 * Prepare a <code>ResultSet</code> for a specified SQL string and
223 * the specified parameters. The specified parameters array
224 * must contain the parameters in the correct order starting with 0 as
225 * the first parameter. Please keep in mind that parameters in
226 * <code>PreparedStatement</code> objects start with 1 as the first
227 * parameter. So <code>parameters[0]</code> maps to the
228 * parameter with index 1.
229 * Please note that you can modify the match parameters with
230 * {@link #setCaseSensitive}, {@link #setExactMatch} and
231 * {@link #setUseRegularExpressions} and the match parameters for the
232 * specified parameter list with {@link #setExactMatchParameter}.
233 * @param sql the SQL string
234 * @param resultSet the corresponding {@link MockResultSet}
235 * @param parameters the parameters
236 */
237 public void prepareResultSet(String sql, MockResultSet resultSet, Object[] parameters)
238 {
239 prepareResultSet(sql, resultSet, ArrayUtil.getListFromObjectArray(parameters));
240 }
241
242 /**
243 * Prepare a <code>ResultSet</code> for a specified SQL string and
244 * the specified parameters. The specified parameters <code>List</code>
245 * must contain the parameters in the correct order starting with 0 as
246 * the first parameter. Please keep in mind that parameters in
247 * <code>PreparedStatement</code> objects start with 1 as the first
248 * parameter. So <code>parameters.get(0)</code> maps to the
249 * parameter with index 1.
250 * Please note that you can modify the match parameters with
251 * {@link #setCaseSensitive}, {@link #setExactMatch} and
252 * {@link #setUseRegularExpressions} and the match parameters for the
253 * specified parameter list with {@link #setExactMatchParameter}.
254 * @param sql the SQL string
255 * @param resultSet the corresponding {@link MockResultSet}
256 * @param parameters the parameters
257 */
258 public void prepareResultSet(String sql, MockResultSet resultSet, List parameters)
259 {
260 Map params = new HashMap();
261 for(int ii = 0; ii < parameters.size(); ii++)
262 {
263 params.put(new Integer(ii + 1), parameters.get(ii));
264 }
265 prepareResultSet(sql, resultSet, params);
266 }
267
268 /**
269 * Prepare a <code>ResultSet</code> for a specified SQL string and
270 * the specified parameters. The specified parameters <code>Map</code>
271 * must contain the parameters by mapping <code>Integer</code> objects
272 * to the corresponding parameter. The <code>Integer</code> object
273 * is the index of the parameter. In the case of a <code>CallableStatement</code>
274 * there are also allowed <code>String</code> keys for named parameters.
275 * Please note that you can modify the match parameters with
276 * {@link #setCaseSensitive}, {@link #setExactMatch} and
277 * {@link #setUseRegularExpressions} and the match parameters for the
278 * specified parameter list with {@link #setExactMatchParameter}.
279 * @param sql the SQL string
280 * @param resultSet the corresponding {@link MockResultSet}
281 * @param parameters the parameters
282 */
283 public void prepareResultSet(String sql, MockResultSet resultSet, Map parameters)
284 {
285 List list = (List)resultSetsForStatement.get(sql);
286 if(null == list)
287 {
288 list = new ArrayList();
289 resultSetsForStatement.put(sql, list);
290 }
291 list.add(new MockResultSetWrapper(resultSet, parameters));
292 }
293
294 /**
295 * Prepare if the specified SQL string with the specified parameters
296 * should raise an exception.
297 * This can be used to simulate database exceptions.
298 * The specified parameters array must contain the parameters in
299 * the correct order starting with 0 as the first parameter.
300 * Please keep in mind that parameters in <code>PreparedStatement</code>
301 * objects start with 1 as the first parameter. So <code>parameters[0]</code>
302 * maps to the parameter with index 1.
303 * Please note that you can modify the match parameters with
304 * {@link #setCaseSensitive}, {@link #setExactMatch} and
305 * {@link #setUseRegularExpressions} and the match parameters for the
306 * specified parameter list with {@link #setExactMatchParameter}.
307 * @param sql the SQL string
308 * @param parameters the parameters
309 */
310 public void prepareThrowsSQLException(String sql, Object[] parameters)
311 {
312 prepareThrowsSQLException(sql, ArrayUtil.getListFromObjectArray(parameters));
313 }
314
315 /**
316 * Prepare if the specified SQL string with the specified parameters
317 * should raise an exception.
318 * This can be used to simulate database exceptions.
319 * The specified parameters <code>List</code> must contain the
320 * parameters in the correct order starting with 0 as the first
321 * parameter. Please keep in mind that parameters in
322 * <code>PreparedStatement</code> objects start with 1 as the first
323 * parameter. So <code>parameters.get(0)</code> maps to the parameter
324 * with index 1.
325 * Please note that you can modify the match parameters with
326 * {@link #setCaseSensitive}, {@link #setExactMatch} and
327 * {@link #setUseRegularExpressions} and the match parameters for the
328 * specified parameter list with {@link #setExactMatchParameter}.
329 * @param sql the SQL string
330 * @param parameters the parameters
331 */
332 public void prepareThrowsSQLException(String sql, List parameters)
333 {
334 Map params = new HashMap();
335 for(int ii = 0; ii < parameters.size(); ii++)
336 {
337 params.put(new Integer(ii + 1), parameters.get(ii));
338 }
339 prepareThrowsSQLException(sql, params);
340 }
341
342 /**
343 * Prepare if the specified SQL string with the specified parameters
344 * should raise an exception.
345 * This can be used to simulate database exceptions.
346 * Please note that you can modify the match parameters with
347 * {@link #setCaseSensitive}, {@link #setExactMatch} and
348 * {@link #setUseRegularExpressions} and the match parameters for the
349 * specified parameter list with {@link #setExactMatchParameter}.
350 * @param sql the SQL string
351 * @param parameters the parameters
352 */
353 public void prepareThrowsSQLException(String sql, Map parameters)
354 {
355 List list = (List)throwsSQLException.get(sql);
356 if(null == list)
357 {
358 list = new ArrayList();
359 throwsSQLException.put(sql, list);
360 }
361 list.add(parameters);
362 }
363
364 /**
365 * Prepare the update count for execute update calls for a specified SQL string
366 * and the specified parameters. The specified parameters array
367 * must contain the parameters in the correct order starting with 0 as
368 * the first parameter. Please keep in mind that parameters in
369 * <code>PreparedStatement</code> objects start with 1 as the first
370 * parameter. So <code>parameters[0]</code> maps to the
371 * parameter with index 1.
372 * Please note that you can modify the match parameters with
373 * {@link #setCaseSensitive}, {@link #setExactMatch} and
374 * {@link #setUseRegularExpressions} and the match parameters for the
375 * specified parameter list with {@link #setExactMatchParameter}.
376 * @param sql the SQL string
377 * @param updateCount the update count
378 * @param parameters the parameters
379 */
380 public void prepareUpdateCount(String sql, int updateCount, Object[] parameters)
381 {
382 prepareUpdateCount(sql, updateCount, ArrayUtil.getListFromObjectArray(parameters));
383 }
384
385 /**
386 * Prepare the update count for execute update calls for a specified SQL string
387 * and the specified parameters. The specified parameters <code>List</code>
388 * must contain the parameters in the correct order starting with 0 as
389 * the first parameter. Please keep in mind that parameters in
390 * <code>PreparedStatement</code> objects start with 1 as the first
391 * parameter. So <code>parameters.get(0)</code> maps to the
392 * parameter with index 1.
393 * Please note that you can modify the match parameters with
394 * {@link #setCaseSensitive}, {@link #setExactMatch} and
395 * {@link #setUseRegularExpressions} and the match parameters for the
396 * specified parameter list with {@link #setExactMatchParameter}.
397 * @param sql the SQL string
398 * @param updateCount the update count
399 * @param parameters the parameters
400 */
401 public void prepareUpdateCount(String sql, int updateCount, List parameters)
402 {
403 Map params = new HashMap();
404 for(int ii = 0; ii < parameters.size(); ii++)
405 {
406 params.put(new Integer(ii + 1), parameters.get(ii));
407 }
408 prepareUpdateCount(sql, updateCount, params);
409 }
410
411 /**
412 * Prepare the update count for execute update calls for a specified SQL string
413 * and the specified parameters. The specified parameters <code>Map</code>
414 * must contain the parameters by mapping <code>Integer</code> objects
415 * to the corresponding parameter. The <code>Integer</code> object
416 * is the index of the parameter. In the case of a <code>CallableStatement</code>
417 * there are also allowed <code>String</code> keys for named parameters.
418 * Please note that you can modify the match parameters with
419 * {@link #setCaseSensitive}, {@link #setExactMatch} and
420 * {@link #setUseRegularExpressions} and the match parameters for the
421 * specified parameter list with {@link #setExactMatchParameter}.
422 * @param sql the SQL string
423 * @param updateCount the update count
424 * @param parameters the parameters
425 */
426 public void prepareUpdateCount(String sql, int updateCount, Map parameters)
427 {
428 List list = (List)updateCountForStatement.get(sql);
429 if(null == list)
430 {
431 list = new ArrayList();
432 updateCountForStatement.put(sql, list);
433 }
434 list.add(new MockUpdateCountWrapper(updateCount, parameters));
435 }
436
437 private class MockResultSetWrapper
438 {
439 private MockResultSet resultSet;
440 private Map parameters;
441
442 public MockResultSetWrapper(MockResultSet resultSet, Map parameters)
443 {
444 this.resultSet = resultSet;
445 this.parameters = parameters;
446 }
447
448 public Map getParamters()
449 {
450 return parameters;
451 }
452
453 public MockResultSet getResultSet()
454 {
455 return resultSet;
456 }
457 }
458
459 private class MockUpdateCountWrapper
460 {
461 private Integer updateCount;
462 private Map parameters;
463
464 public MockUpdateCountWrapper(int updateCount, Map parameters)
465 {
466 this.updateCount = new Integer(updateCount);
467 this.parameters = parameters;
468 }
469
470 public Map getParamters()
471 {
472 return parameters;
473 }
474
475 public Integer getUpdateCount()
476 {
477 return updateCount;
478 }
479 }
480 }