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