001    package com.mockrunner.jdbc;
002    
003    import java.sql.SQLException;
004    import java.util.ArrayList;
005    import java.util.Arrays;
006    import java.util.Collections;
007    import java.util.HashMap;
008    import java.util.Iterator;
009    import java.util.List;
010    import java.util.Map;
011    import java.util.TreeMap;
012    
013    import com.mockrunner.mock.jdbc.MockResultSet;
014    import com.mockrunner.util.common.ArrayUtil;
015    
016    /**
017     * Abstract base class for all statement types
018     * that support parameters, i.e. <code>PreparedStatement</code>
019     * and <code>CallableStatement</code>.
020     */
021    public abstract class AbstractParameterResultSetHandler extends AbstractResultSetHandler
022    {
023        private boolean exactMatchParameter = false;
024        private Map resultSetsForStatement = new TreeMap();
025        private Map updateCountForStatement = new TreeMap();
026        private Map throwsSQLException = new TreeMap();
027        private Map generatedKeysForStatement = new TreeMap();
028            private Map executedStatementParameters = new TreeMap();
029        
030            /**
031             * Collects all SQL strings that were executed.
032             * @param sql the SQL string
033             * @param parameters a copy of the corresponding parameter map
034             */
035            public void addParameterMapForExecutedStatement(String sql, Map parameters)
036            {
037                    if(null != parameters)
038                    {
039                            if(null == executedStatementParameters.get(sql))
040                            {
041                                    executedStatementParameters.put(sql, new ParameterSets(sql));
042                            }
043                            ParameterSets sets = (ParameterSets)executedStatementParameters.get(sql);
044                            sets.addParameterSet(parameters);
045                    }
046            }
047            
048            /**
049             * Returns the <code>ParameterSets</code> for a specified
050             * SQL string.
051             * @param sql the SQL string
052             * @return the <code>Map</code> of parameters
053             */
054            public ParameterSets getParametersForExecutedStatement(String sql)
055            {
056                    return (ParameterSets)executedStatementParameters.get(sql);
057            }
058            
059            /**
060             * Returns the <code>Map</code> of executed SQL strings.
061             * Each string maps to the corresponding {@link ParameterSets}
062             * object.
063             * @return the <code>Map</code> of parameters
064             */
065            public Map getExecutedStatementParameterMap()
066            {
067                    return Collections.unmodifiableMap(executedStatementParameters);
068            }
069        
070        /**
071         * @deprecated use {@link #getExecutedStatementParameterMap}
072         */
073        public Map getExecutedStatementParameter()
074        {
075            return getExecutedStatementParameterMap();
076        }
077        
078        /**
079         * Sets if the specified parameters must match exactly
080         * in order and number.
081         * Defaults to <code>false</code>, i.e. the specified
082         * parameters must be present in the actual parameter
083         * list of the prepared statement with the correct index
084         * but it's ok if there are more actual parameters.
085         * @param exactMatchParameter must parameters match exactly
086         */
087        public void setExactMatchParameter(boolean exactMatchParameter)
088        {
089            this.exactMatchParameter = exactMatchParameter;
090        }
091    
092        /**
093         * Returns the first update count that matches the
094         * specified SQL string and the specified parameters.
095         * If the specified SQL string was prepared to return multiple update 
096         * counts, the first one will be returned.
097         * Please note that you can modify the match parameters with 
098         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
099         * {@link #setUseRegularExpressions} and the match parameters for the 
100         * specified parameter list with {@link #setExactMatchParameter}.
101         * @param sql the SQL string
102         * @param parameters the parameters
103         * @return the corresponding update count
104         */
105        public Integer getUpdateCount(String sql, Map parameters)
106        {
107            Integer[] updateCounts = getUpdateCounts(sql, parameters);
108            if(null != updateCounts && updateCounts.length > 0)
109            {
110                return updateCounts[0];
111            }
112            return null;
113        }
114        
115        /**
116         * Returns the first update count array that matches the
117         * specified SQL string and the specified parameters. 
118         * If the specified SQL string was prepared to return one update count, 
119         * this value will be wrapped in an array with one element.
120         * Please note that you can modify the match parameters with 
121         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
122         * {@link #setUseRegularExpressions} and the match parameters for the 
123         * specified parameter list with {@link #setExactMatchParameter}.
124         * @param sql the SQL string
125         * @param parameters the parameters
126         * @return the corresponding update count
127         */
128        public Integer[] getUpdateCounts(String sql, Map parameters)
129        {
130            ParameterWrapper wrapper = (ParameterWrapper)getMatchingParameterWrapper(sql, parameters, updateCountForStatement);
131            if(null != wrapper)
132            {
133                if(wrapper instanceof MockUpdateCountWrapper)
134                {
135                    return new Integer[] {((MockUpdateCountWrapper)wrapper).getUpdateCount()};
136                }
137                else if(wrapper instanceof MockUpdateCountArrayWrapper)
138                {
139                    return ((MockUpdateCountArrayWrapper)wrapper).getUpdateCount();
140                }
141            }
142            return null;
143        }
144        
145        /**
146         * Returns the if the specified SQL string with the specified parameters
147         * returns multiple update counts.
148         * Please note that you can modify the match parameters with {@link #setCaseSensitive},
149         * {@link #setExactMatch} and {@link #setUseRegularExpressions}.
150         * @param sql the SQL string
151         * @return <code>true</code> if the SQL string returns multiple update counts,
152         *         <code>false</code> otherwise
153         */
154        public boolean hasMultipleUpdateCounts(String sql, Map parameters)
155        {
156            ParameterWrapper wrapper = (ParameterWrapper)getMatchingParameterWrapper(sql, parameters, updateCountForStatement);
157            return (wrapper instanceof MockUpdateCountArrayWrapper);
158        }
159    
160        /**
161         * Returns the first <code>ResultSet</code> that matches the
162         * specified SQL string and the specified parameters.
163         * If the specified SQL string was prepared to return multiple result 
164         * sets, the first one will be returned.
165         * Please note that you can modify the match parameters with 
166         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
167         * {@link #setUseRegularExpressions} and the match parameters for the 
168         * specified parameter list with {@link #setExactMatchParameter}.
169         * @param sql the SQL string
170         * @param parameters the parameters
171         * @return the corresponding {@link MockResultSet}
172         */
173        public MockResultSet getResultSet(String sql, Map parameters)
174        {
175            MockResultSet[] resultSets = getResultSets(sql, parameters);
176            if(null != resultSets && resultSets.length > 0)
177            {
178                return resultSets[0];
179            }
180            return null;
181        }
182        
183        /**
184         * Returns the first <code>ResultSet[]</code> that matches the
185         * specified SQL string and the specified parameters. 
186         * If the specified SQL string was prepared to return one single 
187         * <code>ResultSet</code>, this <code>ResultSet</code> will be wrapped 
188         * in an array with  one element.
189         * Please note that you can modify the match parameters with 
190         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
191         * {@link #setUseRegularExpressions} and the match parameters for the 
192         * specified parameter list with {@link #setExactMatchParameter}.
193         * @param sql the SQL string
194         * @param parameters the parameters
195         * @return the corresponding update count
196         */
197        public MockResultSet[] getResultSets(String sql, Map parameters)
198        {
199            ParameterWrapper wrapper = (ParameterWrapper)getMatchingParameterWrapper(sql, parameters, resultSetsForStatement);
200            if(null != wrapper)
201            {
202                if(wrapper instanceof MockResultSetWrapper)
203                {
204                    return new MockResultSet[] {((MockResultSetWrapper)wrapper).getResultSet()};
205                }
206                else if(wrapper instanceof MockResultSetArrayWrapper)
207                {
208                    return ((MockResultSetArrayWrapper)wrapper).getResultSets();
209                }
210            }
211            return null;
212        }
213        
214        /**
215         * Returns the if the specified SQL string with the specified parameters
216         * returns multiple result sets.
217         * Please note that you can modify the match parameters with {@link #setCaseSensitive},
218         * {@link #setExactMatch} and {@link #setUseRegularExpressions}.
219         * @param sql the SQL string
220         * @return <code>true</code> if the SQL string returns multiple update counts,
221         *         <code>false</code> otherwise
222         */
223        public boolean hasMultipleResultSets(String sql, Map parameters)
224        {
225            ParameterWrapper wrapper = (ParameterWrapper)getMatchingParameterWrapper(sql, parameters, resultSetsForStatement);
226            return (wrapper instanceof MockResultSetArrayWrapper);
227        }
228        
229        /**
230         * Returns if the specified SQL string with the specified parameters
231         * should raise an exception.
232         * This can be used to simulate database exceptions.
233         * Please note that you can modify the match parameters with 
234         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
235         * {@link #setUseRegularExpressions} and the match parameters for the 
236         * specified parameter list with {@link #setExactMatchParameter}.
237         * @param sql the SQL string
238         * @param parameters the parameters
239         * @return <code>true</code> if the specified SQL string should raise an exception,
240         *         <code>false</code> otherwise
241         */
242        public boolean getThrowsSQLException(String sql, Map parameters)
243        {
244            return (getSQLException(sql, parameters) != null);
245        }
246        
247        /**
248         * Returns the <code>SQLException</code> the specified SQL string
249         * should throw. Returns <code>null</code> if the specified SQL string
250         * should not throw an exception.
251         * This can be used to simulate database exceptions.
252         * Please note that you can modify the match parameters with 
253         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
254         * {@link #setUseRegularExpressions} and the match parameters for the 
255         * specified parameter list with {@link #setExactMatchParameter}.
256         * @param sql the SQL string
257         * @param parameters the parameters
258         * @return the <code>SQLException</code> or <code>null</code>
259         */
260        public SQLException getSQLException(String sql, Map parameters)
261        {
262            MockSQLExceptionWrapper wrapper = (MockSQLExceptionWrapper)getMatchingParameterWrapper(sql, parameters, throwsSQLException);
263            if(null != wrapper)
264            {
265                return wrapper.getException();
266            }
267            return null;
268        }
269        
270        /**
271         * Returns the first generated keys <code>ResultSet</code> that 
272         * matches the specified SQL string. 
273         * Please note that you can modify the match parameters with 
274         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
275         * {@link #setUseRegularExpressions} and the match parameters for the 
276         * specified parameter list with {@link #setExactMatchParameter}.
277         * @param sql the SQL string
278         * @param parameters the parameters
279         * @return the corresponding generated keys {@link MockResultSet}
280         */
281        public MockResultSet getGeneratedKeys(String sql, Map parameters)
282        {
283            MockResultSetWrapper wrapper = (MockResultSetWrapper)getMatchingParameterWrapper(sql, parameters, generatedKeysForStatement);
284            if(null != wrapper)
285            {
286                return wrapper.getResultSet();
287            }
288            return null;
289        }
290    
291        protected ParameterWrapper getMatchingParameterWrapper(String sql, Map parameters, Map statementMap)
292        {
293            SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions());
294            List list = matcher.getMatchingObjects(statementMap, sql, true, true);
295            for(int ii = 0; ii < list.size(); ii++)
296            {
297                ParameterWrapper wrapper = (ParameterWrapper)list.get(ii);
298                if(doParameterMatch(wrapper.getParamters(), parameters))
299                {
300                    return wrapper;
301                }
302            }
303            return null;
304        }
305        
306        private boolean doParameterMatch(Map expectedParameters, Map actualParameters)
307        {
308            if(exactMatchParameter)
309            {
310                if(actualParameters.size() != expectedParameters.size()) return false;
311                Iterator iterator = actualParameters.keySet().iterator();
312                while(iterator.hasNext())
313                {
314                    Object currentKey = iterator.next();
315                    if(!actualParameters.containsKey(currentKey)) return false;
316                    Object expectedObject = expectedParameters.get(currentKey);
317                    if(!ParameterUtil.compareParameter(actualParameters.get(currentKey), expectedObject))
318                    {
319                        return false;
320                    }
321                }
322                return true;
323            }
324            else
325            {
326                Iterator iterator = expectedParameters.keySet().iterator();
327                while(iterator.hasNext())
328                {
329                    Object currentKey = iterator.next();
330                    if(!actualParameters.containsKey(currentKey)) return false;
331                    Object actualObject = actualParameters.get(currentKey);
332                    if(!ParameterUtil.compareParameter(actualObject, expectedParameters.get(currentKey)))
333                    {
334                        return false;
335                    }
336                }
337                return true;
338            }
339        }
340    
341        /**
342         * Clears the <code>ResultSet</code> objects.
343         */
344        public void clearResultSets()
345        {
346            super.clearResultSets();
347            resultSetsForStatement.clear();
348        }
349        
350        /**
351         * Clears the update counts.
352         */
353        public void clearUpdateCounts()
354        {
355            super.clearUpdateCounts();
356            updateCountForStatement.clear();
357        }
358        
359        /**
360         * Clears the list of statements that should throw an exception
361         */
362        public void clearThrowsSQLException()
363        {
364            super.clearThrowsSQLException();
365            throwsSQLException.clear();
366        }
367        
368        /**
369         * Clears the list of statements that return generated keys.
370         */
371        public void clearGeneratedKeys()
372        {
373            super.clearGeneratedKeys();
374            generatedKeysForStatement.clear();
375        }
376    
377        /**
378         * Prepare a <code>ResultSet</code> for a specified SQL string and
379         * the specified parameters. The specified parameters array
380         * must contain the parameters in the correct order starting with index 0 for
381         * the first parameter. Please keep in mind that parameters in
382         * <code>PreparedStatement</code> objects start with 1 as the first
383         * parameter. So <code>parameters[0]</code> maps to the
384         * parameter with index 1.
385         * Please note that you can modify the match parameters with 
386         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
387         * {@link #setUseRegularExpressions} and the match parameters for the 
388         * specified parameter list with {@link #setExactMatchParameter}.
389         * @param sql the SQL string
390         * @param resultSet the corresponding {@link MockResultSet}
391         * @param parameters the parameters
392         */
393        public void prepareResultSet(String sql, MockResultSet resultSet, Object[] parameters)
394        {
395            prepareResultSet(sql, resultSet, Arrays.asList(parameters));
396        }
397        
398        /**
399         * Prepare an array of <code>ResultSet</code> objects for a specified SQL string and
400         * the specified parameters. This method can be used for queries that return 
401         * multiple result sets. The specified parameters array
402         * must contain the parameters in the correct order starting with index 0 for
403         * the first parameter. Please keep in mind that parameters in
404         * <code>PreparedStatement</code> objects start with 1 as the first
405         * parameter. So <code>parameters[0]</code> maps to the
406         * parameter with index 1.
407         * Please note that you can modify the match parameters with 
408         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
409         * {@link #setUseRegularExpressions} and the match parameters for the 
410         * specified parameter list with {@link #setExactMatchParameter}.
411         * @param sql the SQL string
412         * @param resultSets the corresponding <code>MockResultSet[]</code>
413         * @param parameters the parameters
414         */
415        public void prepareResultSets(String sql, MockResultSet[] resultSets, Object[] parameters)
416        {
417            prepareResultSets(sql, resultSets, Arrays.asList(parameters));
418        }
419    
420        /**
421         * Prepare a <code>ResultSet</code> for a specified SQL string and
422         * the specified parameters. The specified parameters <code>List</code>
423         * must contain the parameters in the correct order starting with index 0 for
424         * the first parameter. Please keep in mind that parameters in
425         * <code>PreparedStatement</code> objects start with 1 as the first
426         * parameter. So <code>parameters.get(0)</code> maps to the
427         * parameter with index 1.
428         * Please note that you can modify the match parameters with 
429         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
430         * {@link #setUseRegularExpressions} and the match parameters for the 
431         * specified parameter list with {@link #setExactMatchParameter}.
432         * @param sql the SQL string
433         * @param resultSet the corresponding {@link MockResultSet}
434         * @param parameters the parameters
435         */
436        public void prepareResultSet(String sql, MockResultSet resultSet, List parameters)
437        {
438            Map params = createParameterMap(parameters);
439            prepareResultSet(sql, resultSet, params);
440        }
441        
442        /**
443         * Prepare an array of <code>ResultSet</code> objects for a specified SQL string and
444         * the specified parameters. This method can be used for queries that return 
445         * multiple result sets. The specified parameters <code>List</code>
446         * must contain the parameters in the correct order starting with index 0 for
447         * the first parameter. Please keep in mind that parameters in
448         * <code>PreparedStatement</code> objects start with 1 as the first
449         * parameter. So <code>parameters.get(0)</code> maps to the
450         * parameter with index 1.
451         * Please note that you can modify the match parameters with 
452         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
453         * {@link #setUseRegularExpressions} and the match parameters for the 
454         * specified parameter list with {@link #setExactMatchParameter}.
455         * @param sql the SQL string
456         * @param resultSets the corresponding <code>MockResultSet[]</code>
457         * @param parameters the parameters
458         */
459        public void prepareResultSets(String sql, MockResultSet[] resultSets, List parameters)
460        {
461            Map params = createParameterMap(parameters);
462            prepareResultSets(sql, resultSets, params);
463        }
464        
465        /**
466         * Prepare a <code>ResultSet</code> for a specified SQL string and
467         * the specified parameters. The specified parameters <code>Map</code>
468         * must contain the parameters by mapping <code>Integer</code> objects
469         * to the corresponding parameter. The <code>Integer</code> object
470         * is the index of the parameter. In the case of a <code>CallableStatement</code>,
471         * <code>String</code> keys for named parameters are also allowed.
472         * Please note that you can modify the match parameters with 
473         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
474         * {@link #setUseRegularExpressions} and the match parameters for the 
475         * specified parameter list with {@link #setExactMatchParameter}.
476         * @param sql the SQL string
477         * @param resultSet the corresponding {@link MockResultSet}
478         * @param parameters the parameters
479         */
480        public void prepareResultSet(String sql, MockResultSet resultSet, Map parameters)
481        {
482            List list = getListFromMapForSQLStatement(sql, resultSetsForStatement);
483            list.add(new MockResultSetWrapper(resultSet, new HashMap(parameters)));
484        }
485        
486        /**
487         * Prepare an array of <code>ResultSet</code> objects for a specified SQL string and
488         * the specified parameters. This method can be used for queries that return 
489         * multiple result sets. The specified parameters <code>Map</code>
490         * must contain the parameters by mapping <code>Integer</code> objects
491         * to the corresponding parameter. The <code>Integer</code> object
492         * is the index of the parameter. In the case of a <code>CallableStatement</code>,
493         * <code>String</code> keys for named parameters are also allowed.
494         * Please note that you can modify the match parameters with 
495         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
496         * {@link #setUseRegularExpressions} and the match parameters for the 
497         * specified parameter list with {@link #setExactMatchParameter}.
498         * @param sql the SQL string
499         * @param resultSets the corresponding <code>MockResultSet[]</code>
500         * @param parameters the parameters
501         */
502        public void prepareResultSets(String sql, MockResultSet[] resultSets, Map parameters)
503        {
504            List list = getListFromMapForSQLStatement(sql, resultSetsForStatement);
505            list.add(new MockResultSetArrayWrapper((MockResultSet[])resultSets.clone(), new HashMap(parameters)));
506        }
507        
508        /**
509         * Prepare that the specified SQL string with the specified parameters
510         * should raise an exception.
511         * This can be used to simulate database exceptions.
512         * This method creates an <code>SQLException</code> and will throw this 
513         * exception. With {@link #prepareThrowsSQLException(String, SQLException, Object[])} 
514         * you can specify the exception.
515         * The specified parameters array must contain the parameters in 
516         * the correct order starting with index 0 for the first parameter. 
517         * Please keep in mind that parameters in <code>PreparedStatement</code> 
518         * objects start with 1 as the first parameter. So <code>parameters[0]</code> 
519         * maps to the parameter with index 1.
520         * Please note that you can modify the match parameters with 
521         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
522         * {@link #setUseRegularExpressions} and the match parameters for the 
523         * specified parameter list with {@link #setExactMatchParameter}.
524         * @param sql the SQL string
525         * @param parameters the parameters
526         */
527        public void prepareThrowsSQLException(String sql, Object[] parameters)
528        {
529            SQLException exc = new SQLException("Statement " + sql + " was specified to throw an exception");
530            prepareThrowsSQLException(sql, exc, parameters);
531        }
532        
533        /**
534         * Prepare that the specified SQL string with the specified parameters
535         * should raise an exception.
536         * This can be used to simulate database exceptions.
537         * This method creates an <code>SQLException</code> and will throw this 
538         * exception. With {@link #prepareThrowsSQLException(String, SQLException, List)} 
539         * you can specify the exception.
540         * The specified parameters <code>List</code> must contain the 
541         * parameters in the correct order starting with index 0 for the first 
542         * parameter. Please keep in mind that parameters in 
543         * <code>PreparedStatement</code> objects start with 1 as the first
544         * parameter. So <code>parameters.get(0)</code> maps to the parameter 
545         * with index 1.
546         * Please note that you can modify the match parameters with 
547         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
548         * {@link #setUseRegularExpressions} and the match parameters for the 
549         * specified parameter list with {@link #setExactMatchParameter}.
550         * @param sql the SQL string
551         * @param parameters the parameters
552         */
553        public void prepareThrowsSQLException(String sql, List parameters)
554        {
555            SQLException exc = new SQLException("Statement " + sql + " was specified to throw an exception");
556            prepareThrowsSQLException(sql, exc, parameters);
557        }
558        
559        /**
560         * Prepare that the specified SQL string with the specified parameters
561         * should raise an exception.
562         * This can be used to simulate database exceptions.
563         * This method creates an <code>SQLException</code> and will throw this 
564         * exception. With {@link #prepareThrowsSQLException(String, SQLException, Map)} 
565         * you can specify the exception.
566         * The specified parameters <code>Map</code> must contain the parameters by 
567         * mapping <code>Integer</code> objects to the corresponding parameter. 
568         * The <code>Integer</code> object is the index of the parameter. In the case
569         * of a <code>CallableStatement</code>, 
570         * <code>String</code> keys for named parameters are also allowed.
571         * Please note that you can modify the match parameters with 
572         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
573         * {@link #setUseRegularExpressions} and the match parameters for the 
574         * specified parameter list with {@link #setExactMatchParameter}.
575         * @param sql the SQL string
576         * @param parameters the parameters
577         */
578        public void prepareThrowsSQLException(String sql, Map parameters)
579        {
580            SQLException exc = new SQLException("Statement " + sql + " was specified to throw an exception");
581            prepareThrowsSQLException(sql, exc, parameters);
582        }
583        
584        /**
585         * Prepare that the specified SQL string with the specified parameters
586         * should raise an exception.
587         * This can be used to simulate database exceptions.
588         * This method takes an exception object that will be thrown.
589         * The specified parameters array must contain the parameters in 
590         * the correct order starting with index 0 for the first parameter. 
591         * Please keep in mind that parameters in <code>PreparedStatement</code> 
592         * objects start with 1 as the first parameter. So <code>parameters[0]</code> 
593         * maps to the parameter with index 1.
594         * Please note that you can modify the match parameters with 
595         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
596         * {@link #setUseRegularExpressions} and the match parameters for the 
597         * specified parameter list with {@link #setExactMatchParameter}.
598         * @param sql the SQL string
599         * @param exc the <code>SQLException</code> that should be thrown
600         * @param parameters the parameters
601         */
602        public void prepareThrowsSQLException(String sql, SQLException exc, Object[] parameters)
603        {
604            prepareThrowsSQLException(sql, exc, Arrays.asList(parameters));
605        }
606        
607        /**
608         * Prepare that the specified SQL string with the specified parameters
609         * should raise an exception.
610         * This can be used to simulate database exceptions.
611         * This method takes an exception object that will be thrown.
612         * The specified parameters <code>List</code> must contain the 
613         * parameters in the correct order starting with index 0 for the first 
614         * parameter. Please keep in mind that parameters in 
615         * <code>PreparedStatement</code> objects start with 1 as the first
616         * parameter. So <code>parameters.get(0)</code> maps to the parameter 
617         * with index 1.
618         * Please note that you can modify the match parameters with 
619         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
620         * {@link #setUseRegularExpressions} and the match parameters for the 
621         * specified parameter list with {@link #setExactMatchParameter}.
622         * @param sql the SQL string
623         * @param exc the <code>SQLException</code> that should be thrown
624         * @param parameters the parameters
625         */
626        public void prepareThrowsSQLException(String sql, SQLException exc, List parameters)
627        {
628            Map params = createParameterMap(parameters);
629            prepareThrowsSQLException(sql, exc, params);
630        }
631        
632        /**
633         * Prepare that the specified SQL string with the specified parameters
634         * should raise an exception.
635         * This can be used to simulate database exceptions.
636         * This method takes an exception object that will be thrown.
637         * The specified parameters <code>Map</code> must contain the parameters by 
638         * mapping <code>Integer</code> objects to the corresponding parameter. 
639         * The <code>Integer</code> object is the index of the parameter. In the case
640         * of a <code>CallableStatement</code>, 
641         * <code>String</code> keys for named parameters are also allowed.
642         * Please note that you can modify the match parameters with 
643         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
644         * {@link #setUseRegularExpressions} and the match parameters for the 
645         * specified parameter list with {@link #setExactMatchParameter}.
646         * @param sql the SQL string
647         * @param exc the <code>SQLException</code> that should be thrown
648         * @param parameters the parameters
649         */
650        public void prepareThrowsSQLException(String sql, SQLException exc, Map parameters)
651        {
652            List list = getListFromMapForSQLStatement(sql, throwsSQLException);
653            list.add(new MockSQLExceptionWrapper(exc, new HashMap(parameters)));
654        }
655    
656        /**
657         * Prepare the update count for execute update calls for a specified SQL string
658         * and the specified parameters. The specified parameters array
659         * must contain the parameters in the correct order starting with index 0 for
660         * the first parameter. Please keep in mind that parameters in
661         * <code>PreparedStatement</code> objects start with 1 as the first
662         * parameter. So <code>parameters[0]</code> maps to the
663         * parameter with index 1.
664         * Please note that you can modify the match parameters with 
665         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
666         * {@link #setUseRegularExpressions} and the match parameters for the 
667         * specified parameter list with {@link #setExactMatchParameter}.
668         * @param sql the SQL string
669         * @param updateCount the update count
670         * @param parameters the parameters
671         */
672        public void prepareUpdateCount(String sql, int updateCount, Object[] parameters)
673        {
674            prepareUpdateCount(sql, updateCount, Arrays.asList(parameters));
675        }
676        
677        /**
678         * Prepare an array update count values for execute update calls for a specified SQL string
679         * and the specified parameters. This method can be used if multiple update counts
680         * are returned. The specified parameters array
681         * must contain the parameters in the correct order starting with index 0 for
682         * the first parameter. Please keep in mind that parameters in
683         * <code>PreparedStatement</code> objects start with 1 as the first
684         * parameter. So <code>parameters[0]</code> maps to the
685         * parameter with index 1.
686         * Please note that you can modify the match parameters with 
687         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
688         * {@link #setUseRegularExpressions} and the match parameters for the 
689         * specified parameter list with {@link #setExactMatchParameter}.
690         * @param sql the SQL string
691         * @param updateCounts the update count array
692         * @param parameters the parameters
693         */
694        public void prepareUpdateCounts(String sql, int[] updateCounts, Object[] parameters)
695        {
696            prepareUpdateCounts(sql, updateCounts, Arrays.asList(parameters));
697        }
698    
699        /**
700         * Prepare the update count for execute update calls for a specified SQL string
701         * and the specified parameters. The specified parameters <code>List</code>
702         * must contain the parameters in the correct order starting with index 0 for
703         * the first parameter. Please keep in mind that parameters in
704         * <code>PreparedStatement</code> objects start with 1 as the first
705         * parameter. So <code>parameters.get(0)</code> maps to the
706         * parameter with index 1.
707         * Please note that you can modify the match parameters with 
708         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
709         * {@link #setUseRegularExpressions} and the match parameters for the 
710         * specified parameter list with {@link #setExactMatchParameter}.
711         * @param sql the SQL string
712         * @param updateCount the update count
713         * @param parameters the parameters
714         */
715        public void prepareUpdateCount(String sql, int updateCount, List parameters)
716        {
717            Map params = createParameterMap(parameters);
718            prepareUpdateCount(sql, updateCount,  params);
719        }
720        
721        /**
722         * Prepare an array update count values for execute update calls for a specified SQL string
723         * and the specified parameters. This method can be used if multiple update counts
724         * are returned. The specified parameters <code>List</code>
725         * must contain the parameters in the correct order starting with index 0 for
726         * the first parameter. Please keep in mind that parameters in
727         * <code>PreparedStatement</code> objects start with 1 as the first
728         * parameter. So <code>parameters.get(0)</code> maps to the
729         * parameter with index 1.
730         * Please note that you can modify the match parameters with 
731         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
732         * {@link #setUseRegularExpressions} and the match parameters for the 
733         * specified parameter list with {@link #setExactMatchParameter}.
734         * @param sql the SQL string
735         * @param updateCounts the update count array
736         * @param parameters the parameters
737         */
738        public void prepareUpdateCounts(String sql, int[] updateCounts, List parameters)
739        {
740            Map params = createParameterMap(parameters);
741            prepareUpdateCounts(sql, updateCounts,  params);
742        }
743        
744        /**
745         * Prepare the update count for execute update calls for a specified SQL string
746         * and the specified parameters. The specified parameters <code>Map</code>
747         * must contain the parameters by mapping <code>Integer</code> objects
748         * to the corresponding parameter. The <code>Integer</code> object
749         * is the index of the parameter. In the case of a <code>CallableStatement</code>,
750         * <code>String</code> keys for named parameters are also allowed.
751         * Please note that you can modify the match parameters with 
752         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
753         * {@link #setUseRegularExpressions} and the match parameters for the 
754         * specified parameter list with {@link #setExactMatchParameter}.
755         * @param sql the SQL string
756         * @param updateCount the update count
757         * @param parameters the parameters
758         */
759        public void prepareUpdateCount(String sql, int updateCount, Map parameters)
760        {
761            List list = getListFromMapForSQLStatement(sql, updateCountForStatement);
762            list.add(new MockUpdateCountWrapper(updateCount, new HashMap(parameters)));
763        }
764        
765        /**
766         * Prepare an array update count values for execute update calls for a specified SQL string
767         * and the specified parameters. This method can be used if multiple update counts
768         * are returned. The specified parameters <code>Map</code>
769         * must contain the parameters by mapping <code>Integer</code> objects
770         * to the corresponding parameter. The <code>Integer</code> object
771         * is the index of the parameter. In the case of a <code>CallableStatement</code>,
772         * <code>String</code> keys for named parameters are also allowed.
773         * Please note that you can modify the match parameters with 
774         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
775         * {@link #setUseRegularExpressions} and the match parameters for the 
776         * specified parameter list with {@link #setExactMatchParameter}.
777         * @param sql the SQL string
778         * @param updateCounts the update count array
779         * @param parameters the parameters
780         */
781        public void prepareUpdateCounts(String sql, int[] updateCounts, Map parameters)
782        {
783            List list = getListFromMapForSQLStatement(sql, updateCountForStatement);
784            list.add(new MockUpdateCountArrayWrapper((int[])updateCounts.clone(), new HashMap(parameters)));
785        }
786        
787        /**
788         * Prepare the generated keys <code>ResultSet</code> 
789         * for a specified SQL string.
790         * The specified parameters array must contain the parameters in 
791         * the correct order starting with index 0 for the first parameter. 
792         * Please keep in mind that parameters in <code>PreparedStatement</code> 
793         * objects start with 1 as the first parameter. So <code>parameters[0]</code> 
794         * maps to the parameter with index 1.
795         * Please note that you can modify the match parameters with 
796         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
797         * {@link #setUseRegularExpressions} and the match parameters for the 
798         * specified parameter list with {@link #setExactMatchParameter}.
799         * @param sql the SQL string
800         * @param generatedKeysResult the generated keys {@link MockResultSet}
801         * @param parameters the parameters
802         */
803        public void prepareGeneratedKeys(String sql, MockResultSet generatedKeysResult, Object[] parameters)
804        {
805            prepareGeneratedKeys(sql, generatedKeysResult, Arrays.asList(parameters));
806        }
807        
808        /**
809         * Prepare the generated keys <code>ResultSet</code> 
810         * for a specified SQL string.
811         * The specified parameters <code>List</code> must contain the 
812         * parameters in the correct order starting with index 0 for the first 
813         * parameter. Please keep in mind that parameters in 
814         * <code>PreparedStatement</code> objects start with 1 as the first
815         * parameter. So <code>parameters.get(0)</code> maps to the parameter 
816         * with index 1.
817         * Please note that you can modify the match parameters with 
818         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
819         * {@link #setUseRegularExpressions} and the match parameters for the 
820         * specified parameter list with {@link #setExactMatchParameter}.
821         * @param sql the SQL string
822         * @param generatedKeysResult the generated keys {@link MockResultSet}
823         * @param parameters the parameters
824         */
825        public void prepareGeneratedKeys(String sql, MockResultSet generatedKeysResult, List parameters)
826        {
827            Map params = createParameterMap(parameters);
828            prepareGeneratedKeys(sql, generatedKeysResult, params);
829        }
830        
831        /**
832         * Prepare the generated keys <code>ResultSet</code> 
833         * for a specified SQL string.
834         * The specified parameters <code>Map</code> must contain the parameters by 
835         * mapping <code>Integer</code> objects to the corresponding parameter. 
836         * The <code>Integer</code> object is the index of the parameter. In the case
837         * of a <code>CallableStatement</code>, 
838         * <code>String</code> keys for named parameters are also allowed.
839         * Please note that you can modify the match parameters with 
840         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
841         * {@link #setUseRegularExpressions} and the match parameters for the 
842         * specified parameter list with {@link #setExactMatchParameter}.
843         * @param sql the SQL string
844         * @param generatedKeysResult the generated keys {@link MockResultSet}
845         * @param parameters the parameters
846         */
847        public void prepareGeneratedKeys(String sql, MockResultSet generatedKeysResult, Map parameters)
848        {
849            List list = getListFromMapForSQLStatement(sql, generatedKeysForStatement);
850            list.add(new MockResultSetWrapper(generatedKeysResult, new HashMap(parameters)));
851        }
852        
853        private List getListFromMapForSQLStatement(String sql, Map map)
854        {
855            List list = (List)map.get(sql);
856            if(null == list)
857            {
858                list = new ArrayList();
859                map.put(sql, list);
860            }
861            return list;
862        }
863        
864        private Map createParameterMap(List parameters)
865        {
866            Map params = new HashMap();
867            for(int ii = 0; ii < parameters.size(); ii++)
868            {
869                params.put(new Integer(ii + 1), parameters.get(ii));
870            }
871            return params;
872        }
873        
874        protected class ParameterWrapper
875        {
876            private Map parameters;
877            
878            public ParameterWrapper(Map parameters)
879            {
880                this.parameters = parameters;
881            }
882    
883            public Map getParamters()
884            {
885                return parameters;
886            }
887        }
888        
889        private class MockSQLExceptionWrapper extends ParameterWrapper
890        {
891            private SQLException exception;
892            
893        
894            public MockSQLExceptionWrapper(SQLException exception, Map parameters)
895            {
896                super(parameters);
897                this.exception = exception;
898            }
899    
900            public SQLException getException()
901            {
902                return exception;
903            }
904        }
905        
906        private class MockResultSetWrapper extends ParameterWrapper
907        {
908            private MockResultSet resultSet;
909        
910            public MockResultSetWrapper(MockResultSet resultSet, Map parameters)
911            {
912                super(parameters);
913                this.resultSet = resultSet;
914            }
915    
916            public MockResultSet getResultSet()
917            {
918                return resultSet;
919            }
920        }
921        
922        private class MockResultSetArrayWrapper extends ParameterWrapper
923        {
924            private MockResultSet[] resultSets;
925        
926            public MockResultSetArrayWrapper(MockResultSet[] resultSets, Map parameters)
927            {
928                super(parameters);
929                this.resultSets = resultSets;
930            }
931    
932            public MockResultSet[] getResultSets()
933            {
934                return resultSets;
935            }
936        }
937    
938        private class MockUpdateCountWrapper extends ParameterWrapper
939        {
940            private Integer updateCount;
941    
942            public MockUpdateCountWrapper(int updateCount, Map parameters)
943            {
944                super(parameters);
945                this.updateCount = new Integer(updateCount);
946            }
947    
948            public Integer getUpdateCount()
949            {
950                return updateCount;
951            }
952        }
953        
954        private class MockUpdateCountArrayWrapper extends ParameterWrapper
955        {
956            private Integer[] updateCounts;
957    
958            public MockUpdateCountArrayWrapper(int[] updateCounts, Map parameters)
959            {
960                super(parameters);
961                this.updateCounts = (Integer[])ArrayUtil.convertToObjectArray(updateCounts);
962            }
963    
964            public Integer[] getUpdateCount()
965            {
966                return updateCounts;
967            }
968        }
969    }