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.List;
007    import java.util.Map;
008    
009    import com.mockrunner.mock.jdbc.MockResultSet;
010    
011    /**
012     * Abstract base class for all <code>ResultSet</code> handlers.
013     * Used to coordinate <code>ResultSet</code> objects for a
014     * statement. You can use this class to prepare <code>ResultSet</code>
015     * objects and update count values that are returned by the
016     * <code>execute</code> method of a statement, if the current
017     * SQL string matches.
018     * Furthermore it can be used to create <code>ResultSet</code> objects.
019     * Please note that the <code>ResultSet</code> objects you create and
020     * prepare with this handler are cloned when executing statements.
021     * So you cannot rely on object identity. You have to use the id
022     * of the <code>ResultSet</code> to identify it.
023     * The <code>ResultSet</code> objects returned by {@link #getReturnedResultSets}
024     * are actually the instances the executed statements returned.
025     */
026    public abstract class AbstractResultSetHandler
027    {
028        private boolean caseSensitive = false;
029        private boolean exactMatch = false;
030        private boolean useRegularExpressions = false;
031        private MockResultSet globalResultSet;
032        private Map resultSetsForStatement = new HashMap();
033        private int globalUpdateCount = 0;
034        private Map updateCountForStatement = new HashMap();
035        private Map returnsResultSetMap = new HashMap();
036        private List throwsSQLException = new ArrayList();
037        private List executedStatements = new ArrayList();
038        private List returnedResultSets = new ArrayList();
039        
040        /**
041         * Creates a new <code>ResultSet</code> with a
042         * random id.
043         * @return the new <code>ResultSet</code>
044         */
045        public MockResultSet createResultSet()
046        {
047            return new MockResultSet(String.valueOf(Math.random()));
048        }
049        
050        /**
051         * Creates a new <code>ResultSet</code> with the specified id.
052         * @param id the id
053         * @return the new <code>ResultSet</code>
054         */
055        public MockResultSet createResultSet(String id)
056        {
057            return new MockResultSet(id);
058        }
059        
060        /**
061         * Returns a new <code>ResultSet</code> created by
062         * the specified factory. Currently there's only
063         * the {@link FileResultSetFactory} to create <code>ResultSet</code>
064         * objects based on CSV files but you can implement your own factories.
065         * @param factory the {@link ResultSetFactory}
066         * @return the new <code>ResultSet</code>
067         */
068        public MockResultSet createResultSet(ResultSetFactory factory)
069        {
070            return factory.create(String.valueOf(Math.random()));
071        }
072        
073        /**
074         * Returns a new <code>ResultSet</code> created by
075         * the specified factory. Currently there's only
076         * the {@link FileResultSetFactory} to create <code>ResultSet</code>
077         * objects based on CSV files but you can implement your own factories.
078         * Uses a random id.
079         * @param id the id
080         * @param factory the {@link ResultSetFactory}
081         * @return the new <code>ResultSet</code>
082         */
083        public MockResultSet createResultSet(String id, ResultSetFactory factory)
084        {
085            return factory.create(id);
086        }
087        
088        /**
089         * Set if specified SQL strings should be handled case sensitive.
090         * Defaults to to <code>false</code>, i.e. <i>INSERT</i> is the same
091         * as <i>insert</i>.
092         * Please note that this method controls SQL statement
093         * matching for prepared results and update counts, i.e. what
094         * statements the tested application has to execute to receive
095         * a specified result. Unlike {@link JDBCTestModule#setCaseSensitive(boolean)}
096         * it does not control the statement matching of {@link JDBCTestModule}
097         * methods.
098         * @param caseSensitive enable or disable case sensitivity
099         */
100        public void setCaseSensitive(boolean caseSensitive)
101        {
102            this.caseSensitive = caseSensitive;
103        }
104    
105        /**
106         * Set if specified SQL statements must match exactly.
107         * Defaults to <code>false</code>, i.e. the SQL string
108         * does not need to match exactly. If the original statement 
109         * is <i>insert into mytable values(?, ?, ?)</i>
110         * the string <i>insert into mytable</i> will match this statement.
111         * Usually <code>false</code> is the best choice, so
112         * prepared <code>ResultSet</code> objects do not have
113         * to match exactly the current statements SQL string.
114         * Please note that this method controls SQL statement
115         * matching for prepared results and update counts, i.e. what
116         * statements the tested application has to execute to receive
117         * a specified result. Unlike {@link JDBCTestModule#setExactMatch(boolean)}
118         * it does not control the statement matching of {@link JDBCTestModule}
119         * methods.
120         * @param exactMatch enable or disable exact matching
121         */
122        public void setExactMatch(boolean exactMatch)
123        {
124            this.exactMatch = exactMatch;
125        }
126        
127        /**
128         * Set if regular expressions should be used when matching
129         * SQL statements. Irrelevant if <code>exactMatch</code> is
130         * <code>true</code>. Default is <code>false</code>, i.e. you
131         * cannot use regular expressions and matching is based
132         * on string comparison (which is much faster). Enable
133         * this feature only if necessary.
134         * Please note that this method controls SQL statement
135         * matching for prepared results and update counts, i.e. what
136         * statements the tested application has to execute to receive
137         * a specified result. Unlike {@link JDBCTestModule#setUseRegularExpressions(boolean)}
138         * it does not control the statement matching of {@link JDBCTestModule}
139         * methods.
140         * @param useRegularExpressions should regular expressions be used
141         */
142        public void setUseRegularExpressions(boolean useRegularExpressions)
143        {
144            this.useRegularExpressions = useRegularExpressions;
145        }
146        
147        /**
148         * Collects all SQL strings that were executed.
149         * @param sql the SQL string
150         */
151        public void addExecutedStatement(String sql)
152        {
153                    executedStatements.add(sql);
154        }
155        
156        /**
157         * Collects all <code>ResultSet</code> objects that were returned by
158         * a <code>Statement</code>, <code>PreparedStatement</code> or
159         * <code>CallableStatement</code>.
160         * @param resultSet the <code>ResultSet</code>
161         */
162        public void addReturnedResultSet(MockResultSet resultSet)
163        {
164            if(null == resultSet) return;
165            returnedResultSets.add(resultSet);
166        }
167        
168        /**
169         * Returns the <code>List</code> of all executed SQL strings.
170         * @return the <code>List</code> of executed SQL strings
171         */
172        public List getExecutedStatements()
173        {
174            return Collections.unmodifiableList(executedStatements);
175        }
176        
177        /**
178         * Returns the <code>List</code> of all returned <code>ResultSet</code> objects.
179         * @return the <code>List</code> of returned <code>ResultSet</code> objects
180         */
181        public List getReturnedResultSets()
182        {
183            return Collections.unmodifiableList(returnedResultSets);
184        }
185        
186        /**
187         * Clears the <code>ResultSet</code> objects.
188         */
189        public void clearResultSets()
190        {
191            resultSetsForStatement.clear();
192        }
193        
194        /**
195         * Clears the update counts.
196         */
197        public void clearUpdateCounts()
198        {
199            updateCountForStatement.clear();
200        }
201        
202        /**
203         * Clears the definitions if statements return
204         * <code>ResultSet</code> objects or update counts.
205         */
206        public void clearReturnsResultSet()
207        {
208            returnsResultSetMap.clear();
209        }
210        
211        /**
212         * Clears the list of statements that should throw an exception.
213         */
214        public void clearThrowsSQLException()
215        {
216            throwsSQLException.clear();
217        }
218        
219        /**
220         * Returns the <code>Map</code> of all <code>ResultSet</code>
221         * objects, that were added with {@link #prepareResultSet(String, MockResultSet)}.
222         * The SQL strings map to the corresponding <code>ResultSet</code>.
223         * @return the <code>Map</code> of <code>ResultSet</code> objects
224         */
225        public Map getResultSetMap()
226        {
227            return Collections.unmodifiableMap(resultSetsForStatement);
228        }
229        
230        /**
231         * Returns the <code>Map</code> of all update counts, that were added 
232         * with {@link #prepareUpdateCount(String, int)}.
233         * The SQL strings map to the corresponding update count as
234         * <code>Integer</code> object.
235         * @return the <code>Map</code> of <code>ResultSet</code> objects
236         */
237        public Map getUpdateCountMap()
238        {
239            return Collections.unmodifiableMap(updateCountForStatement);
240        }
241        
242        /**
243         * Returns the first <code>ResultSet</code> that matches the
244         * specified SQL string. Please note that you can modify
245         * the match parameters with {@link #setCaseSensitive},
246         * {@link #setExactMatch} and {@link #setUseRegularExpressions}.
247         * @param sql the SQL string
248         * @return the corresponding {@link MockResultSet}
249         */
250        public MockResultSet getResultSet(String sql)
251        {
252            SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions());
253            List list = matcher.getMatchingObjects(resultSetsForStatement, sql, true, true);
254            if(null != list && list.size() > 0)
255            {
256                return (MockResultSet)list.get(0);
257            }
258            return null;
259        }
260        
261        /**
262         * Returns the global <code>ResultSet</code>.
263         * The statement returns the global <code>ResultSet</code>
264         * if no <code>ResultSet</code> can be found for the current
265         * SQL string.
266         * @return the global {@link MockResultSet}
267         */
268        public MockResultSet getGlobalResultSet()
269        {
270            return globalResultSet;
271        }
272        
273        /**
274         * Returns the first update count that matches the
275         * specified SQL string. Please note that you can modify
276         * the match parameters with {@link #setCaseSensitive},
277         * {@link #setExactMatch} and {@link #setUseRegularExpressions}.
278         * @param sql the SQL string
279         * @return the corresponding update count
280         */
281        public Integer getUpdateCount(String sql)
282        {
283            SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions());
284            List list = matcher.getMatchingObjects(updateCountForStatement, sql, true, true);
285            if(null != list && list.size() > 0)
286            {
287                return (Integer)list.get(0);
288            }
289            return null;
290        }
291        
292        /**
293         * Returns the global update count for <code>executeUpdate</code>
294         * calls.
295         * The statement returns the global update count
296         * if no update count can be found for the current
297         * SQL string.
298         * @return the global update count
299         */
300        public int getGlobalUpdateCount()
301        {
302            return globalUpdateCount;
303        }
304        
305        /**
306         * Returns if the specified SQL string is a select that returns
307         * a <code>ResultSet</code>.
308         * Usually you do not have to specify this.
309         * It is assumed that an SQL string returns a <code>ResultSet</code> 
310         * if it contains <i>SELECT</i>.
311         * Please note that you can modify the match parameters with 
312         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
313         * {@link #setUseRegularExpressions}.
314         * @param sql the SQL string
315         * @return <code>true</code> if the SQL string returns a <code>ResultSet</code>
316         */
317        public Boolean getReturnsResultSet(String sql)
318        {
319            SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions());
320            List list = matcher.getMatchingObjects(returnsResultSetMap, sql, true, true);
321            if(null != list && list.size() > 0)
322            {
323                return (Boolean)list.get(0);
324            }
325            return null;
326        }
327        
328        /**
329         * Returns if the specified SQL string should raise an exception.
330         * This can be used to simulate database exceptions.
331         * Please note that you can modify the match parameters with 
332         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
333         * {@link #setUseRegularExpressions}.
334         * @param sql the SQL string
335         * @return <code>true</code> if the specified SQL string should raise an exception,
336         *         <code>false</code> otherwise
337         */
338        public boolean getThrowsSQLException(String sql)
339        {
340            SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions());
341            if(matcher.contains(throwsSQLException, sql, true)) return true;
342            return false;
343        }
344        
345        /**
346         * Prepare a <code>ResultSet</code> for a specified SQL string.
347         * Please note that you can modify the match parameters with 
348         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
349         * {@link #setUseRegularExpressions}.
350         * @param sql the SQL string
351         * @param resultSet the corresponding {@link MockResultSet}
352         */
353        public void prepareResultSet(String sql, MockResultSet resultSet)
354        {
355            resultSetsForStatement.put(sql, resultSet);
356        }
357    
358        /**
359         * Prepare the global <code>ResultSet</code>.
360         * The statement returns the global <code>ResultSet</code>
361         * if no <code>ResultSet</code> can be found for the current
362         * SQL string.
363         * @param resultSet the {@link MockResultSet}
364         */
365        public void prepareGlobalResultSet(MockResultSet resultSet)
366        {
367            this.globalResultSet = resultSet;
368        }
369        
370        /**
371         * Prepare the update count for <code>executeUpdate</code> calls 
372         * for a specified SQL string. Please note that you can modify
373         * the match parameters with {@link #setCaseSensitive},
374         * {@link #setExactMatch} and {@link #setUseRegularExpressions}.
375         * @param sql the SQL string
376         * @param updateCount the update count
377         */
378        public void prepareUpdateCount(String sql, int updateCount)
379        {
380            updateCountForStatement.put(sql, new Integer(updateCount));
381        }
382        
383        /**
384         * Prepare the global update count for <code>executeUpdate</code> calls.
385         * The statement returns the global update count
386         * if no update count can be found for the current
387         * SQL string.
388         * @param updateCount the update count
389         */
390        public void prepareGlobalUpdateCount(int updateCount)
391        {
392            this.globalUpdateCount = updateCount;
393        }
394        
395        /**
396         * Prepare if the specified SQL string is a select that returns
397         * a <code>ResultSet</code>. Usually you do not have to specify this.
398         * It is assumed that an SQL string returns a <code>ResultSet</code> 
399         * if it contains <i>SELECT</i>.
400         * Please note that you can modify the match parameters with 
401         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
402         * {@link #setUseRegularExpressions}.
403         * @param sql the SQL string
404         * @param returnsResultSet specify if the SQL string returns a <code>ResultSet</code>
405         */
406        public void prepareReturnsResultSet(String sql, boolean returnsResultSet)
407        {
408            returnsResultSetMap.put(sql, new Boolean(returnsResultSet));
409        }
410        
411        /**
412         * Prepare if the specified SQL string should raise an exception.
413         * This can be used to simulate database exceptions.
414         * Please note that you can modify the match parameters with 
415         * {@link #setCaseSensitive}, {@link #setExactMatch} and 
416         * {@link #setUseRegularExpressions}.
417         * @param sql the SQL string
418         */
419        public void prepareThrowsSQLException(String sql)
420        {
421            throwsSQLException.add(sql);
422        }
423        
424        /**
425         * Clears the global <code>ResultSet</code>.
426         */
427        public void clearGlobalResultSet()
428        {
429            this.globalResultSet = null;
430        }
431        
432        /**
433         * Returns if specified SQL strings should be handled case sensitive.
434         * @return is case sensitivity enabled or disabled
435         */
436        protected boolean getCaseSensitive()
437        {
438            return caseSensitive;
439        }
440        
441        /**
442         * Returns if specified SQL statements must match exactly.
443         * @return is exact matching enabled or disabled
444         */
445        protected boolean getExactMatch()
446        {
447            return exactMatch;
448        }
449        
450        /**
451         * Returns if regular expression matching is enabled
452         * @return if regular expression matching is enabled
453         */
454        protected boolean getUseRegularExpressions()
455        {
456            return useRegularExpressions;
457        }
458    }