1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package com.gargoylesoftware.base.resource.jdbc;
39
40 import com.gargoylesoftware.base.util.DetailedIllegalArgumentException;
41 import java.lang.reflect.Constructor;
42 import java.lang.reflect.InvocationHandler;
43 import java.lang.reflect.InvocationTargetException;
44 import java.lang.reflect.Method;
45 import java.lang.reflect.Proxy;
46 import java.net.MalformedURLException;
47 import java.net.URL;
48 import java.sql.CallableStatement;
49 import java.sql.Connection;
50 import java.sql.DatabaseMetaData;
51 import java.sql.ParameterMetaData;
52 import java.sql.PreparedStatement;
53 import java.sql.ResultSet;
54 import java.sql.SQLException;
55 import java.sql.SQLWarning;
56 import java.sql.Savepoint;
57 import java.sql.Statement;
58 import java.util.ArrayList;
59 import java.util.HashMap;
60 import java.util.List;
61 import java.util.Map;
62 import junit.framework.TestCase;
63 import junit.framework.TestSuite;
64
65 /***
66 * A TestCase that dynamically generates a seperate test for each method in a
67 * specified interface. This is used to test the various jdbc wrappers.
68 *
69 * @version $Revision: 1.5 $
70 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
71 */
72 public class WrapperTestCase extends TestCase {
73
74 private final Method method_;
75 private final Class interfaceToTest_;
76 private final Constructor wrapperConstructor_;
77 private final boolean isResultWrapped_;
78
79
80 /***
81 * Create an instance
82 *
83 * @param testName
84 * @param interfaceToTest
85 * @param method
86 * @param wrapperConstructor
87 * @param isResultWrapped
88 */
89 private WrapperTestCase(
90 final String testName,
91 final Class interfaceToTest,
92 final Method method,
93 final Constructor wrapperConstructor,
94 final boolean isResultWrapped ) {
95
96 super( testName );
97 method_ = method;
98 interfaceToTest_ = interfaceToTest;
99 wrapperConstructor_ = wrapperConstructor;
100 isResultWrapped_ = isResultWrapped;
101 }
102
103
104 /***
105 * Create a test suite containing a seperate test for each method in
106 * 'interfaceToTest'
107 *
108 * @param interfaceToTest The interface being tested
109 * @param wrapperClass The concrete class that we are testing. This
110 * implements the interface 'interfaceToTest'
111 * @param excludedMethods An array of method names. These methods will not
112 * be tested.
113 * @param wrappedMethods An array of method names. These methods are
114 * expected to return wrapped objects which contain the original result
115 * @return A test suite
116 */
117 public static TestSuite getWrapperTestSuite(
118 final Class interfaceToTest,
119 final Class wrapperClass,
120 final String excludedMethods[],
121 final String wrappedMethods[] ) {
122
123 if( interfaceToTest == null ) {
124 throw new NullPointerException( "interfaceToTest" );
125 }
126 if( excludedMethods == null ) {
127 throw new NullPointerException( "excludedMethods" );
128 }
129 if( wrappedMethods == null ) {
130 throw new NullPointerException( "wrappedMethods" );
131 }
132 if( wrapperClass == null ) {
133 throw new NullPointerException( "wrapperClass" );
134 }
135
136 final Constructor wrapperConstructor;
137 try {
138 wrapperConstructor = wrapperClass.getConstructor( new Class[]{interfaceToTest} );
139 }
140 catch( final NoSuchMethodException e ) {
141 throw new DetailedIllegalArgumentException(
142 "wrapperClass", wrapperClass,
143 "Does not have a constructor that takes an instance of "+interfaceToTest.getName() );
144 }
145
146 String testName;
147
148 final TestSuite suite = new TestSuite();
149 final Method methods[] = interfaceToTest.getMethods();
150 int i;
151 for( i = 0; i < methods.length; i++ ) {
152 if( contains( methods[i], excludedMethods ) == false ) {
153 testName = createTestName( wrapperClass, methods[i] );
154 suite.addTest( new WrapperTestCase( testName,
155 interfaceToTest,
156 methods[i],
157 wrapperConstructor,
158 contains( methods[i], wrappedMethods ) ) );
159 }
160 }
161
162 return suite;
163 }
164
165
166 private static String createTestName( final Class wrapperClass, final Method method ) {
167 final StringBuffer buffer = new StringBuffer( 100 );
168 buffer.append( "Method: " );
169 buffer.append( wrapperClass.getName() );
170 buffer.append( '.' );
171 buffer.append( method.getName() );
172 buffer.append( '(' );
173
174 final Class parameterClasses[] = method.getParameterTypes();
175
176 int i;
177 for( i = 0; i < parameterClasses.length; i++ ) {
178 if( i != 0 ) {
179 buffer.append( "," );
180 }
181
182 buffer.append( parameterClasses[i].getName() );
183 }
184 buffer.append( ")" );
185 return buffer.toString();
186 }
187
188
189 private static boolean contains( final Method method, final String array[] ) {
190 final String methodName = method.getName();
191
192 int i;
193 for( i = 0; i < array.length; i++ ) {
194 if( methodName.equals( array[i] ) ) {
195 return true;
196 }
197 }
198
199 return false;
200 }
201
202
203 /***
204 * During normal operation, the wrapper should allow all method calls to
205 * pass through to the wrapped object. Verify that this is working
206 * correctly.
207 *
208 * @exception Exception If an error occurs
209 */
210 protected void runTest()
211 throws Exception {
212
213 final Method methods[] = interfaceToTest_.getMethods();
214 assertTrue( "the interface has methods", methods.length != 0 );
215
216 final List list = new ArrayList();
217 final InvocationHandler handler =
218 new InvocationHandler() {
219 public Object invoke( Object proxy, Method method, Object[] args ) {
220 if( method.getName().equals( "toString" ) ) {
221 return "Proxy(" + interfaceToTest_.getClass().getName() + ")";
222 }
223
224 list.add( method );
225 if( args != null ) {
226 int i;
227 for( i = 0; i < args.length; i++ ) {
228 list.add( args[i] );
229 }
230 }
231
232 if( method.getReturnType().equals( void.class ) ) {
233 return null;
234 }
235
236 final Object rc = makeObject( method.getReturnType() );
237 list.add( rc );
238 return rc;
239 }
240 };
241
242 final Object proxy = Proxy.newProxyInstance(
243 getClass().getClassLoader(),
244 new Class[]{interfaceToTest_},
245 handler );
246 final Object testObject = wrapperConstructor_.newInstance( new Object[]{proxy} );
247
248 testPassthrough( testObject, method_, list );
249
250 closeObject( testObject );
251
252 try {
253 testPassthrough( testObject, method_, list );
254 fail( "Expected exception when invoking method on a closed object" );
255 }
256 catch( final InvocationTargetException e ) {
257 assertTrue( e.getTargetException() instanceof SQLException );
258 }
259 }
260
261
262 private void testPassthrough(
263 final Object testObject,
264 final Method method,
265 final List list )
266 throws
267 Exception {
268
269 list.clear();
270
271 if( false ) {
272 System.out.println( "ConnectionWrapperTest.testPassthrough() method=" + method );
273 }
274
275 final Class parameterTypes[] = method.getParameterTypes();
276 final Object parameters[] = new Object[parameterTypes.length];
277
278 int i;
279 for( i = 0; i < parameterTypes.length; i++ ) {
280 parameters[i] = makeObject( parameterTypes[i] );
281 }
282
283 final Object rc = method.invoke( testObject, parameters );
284
285 final boolean methodHasReturn = ( method.getReturnType().equals( void.class ) == false );
286 int expectedListSize = parameters.length + 1;
287 if( methodHasReturn ) {
288 expectedListSize++;
289 }
290 assertEquals( "list.size", expectedListSize, list.size() );
291
292 assertEquals( "method", method, list.get( 0 ) );
293 for( i = 0; i < parameters.length; i++ ) {
294 assertEquals( "Parameter[" + i + "]", parameters[i], list.get( i + 1 ) );
295 }
296
297 if( methodHasReturn ) {
298 final Object expectedResult = list.get( i + 1 );
299 assertNotNull( "expectedResult", expectedResult );
300 if( isResultWrapped_ ) {
301 Method getDelegateMethod = null;
302 try {
303 getDelegateMethod = rc.getClass().getMethod( "getDelegate", new Class[0] );
304 }
305 catch( final NoSuchMethodException e ) {
306 fail( "Returned object doesn't have a getDelegate() method. Likely it isn't a wrapper." );
307 }
308 final Object delegate = getDelegateMethod.invoke( rc, new Object[0] );
309
310 assertEquals( "returned delegate", expectedResult, delegate );
311 }
312 else {
313 assertEquals( "returned object", expectedResult, rc );
314 }
315 }
316
317 }
318
319
320 private Object makeObject( final Class clazz ) {
321 if( clazz == int.class ) {
322 return new Integer( 5 );
323 }
324 if( clazz == double.class ) {
325 return new Double( 5.0 );
326 }
327 if( clazz == float.class ) {
328 return new Float( 5 );
329 }
330 if( clazz == byte.class ) {
331 return new Byte( (byte)5 );
332 }
333 if( clazz == short.class ) {
334 return new Short( (short)5 );
335 }
336 if( clazz == long.class ) {
337 return new Long( 5 );
338 }
339 if( clazz == boolean.class ) {
340 return new Boolean( true );
341 }
342 if( clazz == void.class ) {
343 return null;
344 }
345 if( clazz == String.class ) {
346 return new String( "" );
347 }
348 if( clazz == Map.class ) {
349 return new HashMap( 3 );
350 }
351 if( clazz == SQLWarning.class ) {
352 return new SQLWarning();
353 }
354 if( clazz == int[].class ) {
355 return new int[]{1, 2, 3};
356 }
357 if( clazz == byte[].class ) {
358 return new byte[]{};
359 }
360 if( clazz == String[].class ) {
361 return new String[]{};
362 }
363 if( clazz == URL.class ) {
364 try {
365 return new URL("http://foo.com");
366 }
367 catch( final MalformedURLException cantHappenButCompilerRequiresUsToCatchSomething ) {
368 cantHappenButCompilerRequiresUsToCatchSomething.printStackTrace();
369 }
370 }
371
372 if( clazz == Statement.class ) {
373 return makeEmptyProxy( clazz );
374 }
375 if( clazz == PreparedStatement.class ) {
376 return makeEmptyProxy( clazz );
377 }
378 if( clazz == CallableStatement.class ) {
379 return makeEmptyProxy( clazz );
380 }
381 if( clazz == DatabaseMetaData.class ) {
382 return makeEmptyProxy( clazz );
383 }
384 if( clazz == Connection.class ) {
385 return makeEmptyProxy( clazz );
386 }
387 if( clazz == ResultSet.class ) {
388 return makeEmptyProxy( clazz );
389 }
390 if( clazz == Savepoint.class ) {
391 return makeEmptyProxy( clazz );
392 }
393 if( clazz == ParameterMetaData.class ) {
394 return makeEmptyProxy( clazz );
395 }
396
397 if( clazz == java.util.Calendar.class ) {
398 return java.util.Calendar.getInstance();
399 }
400 if( clazz == java.math.BigDecimal.class ) {
401 return new java.math.BigDecimal( 1 );
402 }
403 if( clazz == java.sql.Date.class ) {
404 return new java.sql.Date( 1 );
405 }
406 if( clazz == java.sql.Time.class ) {
407 return new java.sql.Time( 1 );
408 }
409 if( clazz == java.sql.Timestamp.class ) {
410 return new java.sql.Timestamp( 1 );
411 }
412 if( clazz == java.sql.Array.class ) {
413 return makeEmptyProxy( clazz );
414 }
415 if( clazz == java.sql.Ref.class ) {
416 return makeEmptyProxy( clazz );
417 }
418 if( clazz == java.sql.Clob.class ) {
419 return makeEmptyProxy( clazz );
420 }
421 if( clazz == java.sql.Blob.class ) {
422 return makeEmptyProxy( clazz );
423 }
424 if( clazz == java.sql.ResultSetMetaData.class ) {
425 return makeEmptyProxy( clazz );
426 }
427 if( clazz == java.io.InputStream.class ) {
428 return new java.io.ByteArrayInputStream( new byte[0] );
429 }
430 if( clazz == java.io.Reader.class ) {
431 return new java.io.StringReader( "foo" );
432 }
433 if( clazz == Object.class ) {
434 return new Object();
435 }
436
437 System.out.println( "WrapperTestCase.makeObject(" + clazz.getName() + ")" );
438 return null;
439 }
440
441
442 private Object makeEmptyProxy( final Class clazz ) {
443 final InvocationHandler handler =
444 new InvocationHandler() {
445 public Object invoke( Object proxy, Method method, Object[] args ) {
446 final String methodName = method.getName();
447
448 if( methodName.equals( "equals" ) && args.length == 1 ) {
449 return new Boolean( proxy == args[0] );
450 }
451 else if( methodName.equals( "toString" ) ) {
452 return "Proxy(" + clazz.getName() + ")";
453 }
454 return null;
455 }
456 };
457
458 return Proxy.newProxyInstance(
459 getClass().getClassLoader(),
460 new Class[]{clazz},
461 handler );
462 }
463
464
465 private void closeObject( final Object object )
466 throws Exception {
467 final Method closeMethod = object.getClass().getMethod( "close", new Class[0] );
468 closeMethod.invoke( object, new Object[0] );
469 }
470 }
471