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 }