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