20
20
import java .lang .reflect .InvocationHandler ;
21
21
import java .lang .reflect .InvocationTargetException ;
22
22
import java .lang .reflect .Method ;
23
+ import java .util .Arrays ;
23
24
import java .util .HashMap ;
24
25
import java .util .HashSet ;
25
26
import java .util .List ;
26
27
import java .util .Map ;
27
28
import java .util .Set ;
29
+ import java .util .concurrent .ConcurrentHashMap ;
30
+ import java .util .concurrent .ConcurrentMap ;
31
+ import java .util .function .Function ;
28
32
import java .util .stream .Collectors ;
29
33
import net .bytebuddy .ByteBuddy ;
34
+ import net .bytebuddy .description .modifier .Visibility ;
30
35
import net .bytebuddy .dynamic .loading .ClassLoadingStrategy ;
36
+ import net .bytebuddy .implementation .FieldAccessor ;
31
37
import net .bytebuddy .implementation .InvocationHandlerAdapter ;
32
38
import net .bytebuddy .matcher .ElementMatchers ;
33
39
import org .openqa .selenium .Alert ;
183
189
@ Beta
184
190
public class WebDriverDecorator <T extends WebDriver > {
185
191
192
+ protected static class Definition {
193
+ private final Class <?> decoratedClass ;
194
+ private final Class <?> originalClass ;
195
+
196
+ public Definition (Decorated <?> decorated ) {
197
+ this .decoratedClass = decorated .getClass ();
198
+ this .originalClass = decorated .getOriginal ().getClass ();
199
+ }
200
+
201
+ @ Override
202
+ public boolean equals (Object o ) {
203
+ if (o == null || getClass () != o .getClass ()) return false ;
204
+ Definition definition = (Definition ) o ;
205
+ // intentionally an identity check, to ensure we get no false positive lookup due to an
206
+ // unknown implementation of decoratedClass.equals or originalClass.equals
207
+ return (decoratedClass == definition .decoratedClass )
208
+ && (originalClass == definition .originalClass );
209
+ }
210
+
211
+ @ Override
212
+ public int hashCode () {
213
+ return Arrays .hashCode (
214
+ new int [] {
215
+ System .identityHashCode (decoratedClass ), System .identityHashCode (originalClass )
216
+ });
217
+ }
218
+ }
219
+
220
+ public interface HasTarget <Z > {
221
+ Decorated <Z > getTarget ();
222
+
223
+ void setTarget (Decorated <Z > target );
224
+ }
225
+
226
+ protected static class ProxyFactory <T > {
227
+ private final Class <? extends T > clazz ;
228
+
229
+ private ProxyFactory (Class <? extends T > clazz ) {
230
+ this .clazz = clazz ;
231
+ }
232
+
233
+ public T newInstance (Decorated <T > target ) {
234
+ T instance ;
235
+ try {
236
+ instance = (T ) clazz .newInstance ();
237
+ } catch (ReflectiveOperationException e ) {
238
+ throw new AssertionError ("Unable to create new proxy" , e );
239
+ }
240
+
241
+ // ensure we can later find the target to call
242
+ //noinspection unchecked
243
+ ((HasTarget <T >) instance ).setTarget (target );
244
+
245
+ return instance ;
246
+ }
247
+ }
248
+
249
+ private final ConcurrentMap <Definition , ProxyFactory <?>> cache ;
250
+
186
251
private final Class <T > targetWebDriverClass ;
187
252
188
253
private Decorated <T > decorated ;
@@ -194,6 +259,7 @@ public WebDriverDecorator() {
194
259
195
260
public WebDriverDecorator (Class <T > targetClass ) {
196
261
this .targetWebDriverClass = targetClass ;
262
+ this .cache = new ConcurrentHashMap <>();
197
263
}
198
264
199
265
public final T decorate (T original ) {
@@ -295,18 +361,36 @@ private Object decorateResult(Object toDecorate) {
295
361
return toDecorate ;
296
362
}
297
363
298
- protected final <Z > Z createProxy (final Decorated <Z > decorated , Class <Z > clazz ) {
299
- Set <Class <?>> decoratedInterfaces = extractInterfaces (decorated );
300
- Set <Class <?>> originalInterfaces = extractInterfaces (decorated .getOriginal ());
301
- Map <Class <?>, InvocationHandler > derivedInterfaces =
302
- deriveAdditionalInterfaces (decorated .getOriginal ());
364
+ protected final <Z > Z createProxy (final Decorated <Z > decorated , Class <? extends Z > clazz ) {
365
+ @ SuppressWarnings ("unchecked" )
366
+ ProxyFactory <Z > factory =
367
+ (ProxyFactory <Z >)
368
+ cache .computeIfAbsent (
369
+ new Definition (decorated ), (key ) -> createProxyFactory (key , decorated , clazz ));
370
+
371
+ return factory .newInstance (decorated );
372
+ }
373
+
374
+ protected final <Z > ProxyFactory <? extends Z > createProxyFactory (
375
+ Definition definition , final Decorated <Z > sample , Class <? extends Z > clazz ) {
376
+ Set <Class <?>> decoratedInterfaces = extractInterfaces (definition .decoratedClass );
377
+ Set <Class <?>> originalInterfaces = extractInterfaces (definition .originalClass );
378
+ // all samples with the same definition should have the same derivedInterfaces
379
+ Map <Class <?>, Function <Z , InvocationHandler >> derivedInterfaces =
380
+ deriveAdditionalInterfaces (sample .getOriginal ());
303
381
304
382
final InvocationHandler handler =
305
383
(proxy , method , args ) -> {
384
+ // Lookup the instance to call, to reuse the clazz and handler.
385
+ @ SuppressWarnings ("unchecked" )
386
+ Decorated <Z > instance = ((HasTarget <Z >) proxy ).getTarget ();
387
+ if (instance == null ) {
388
+ throw new AssertionError ("Failed to get instance to call" );
389
+ }
306
390
try {
307
391
if (method .getDeclaringClass ().equals (Object .class )
308
392
|| decoratedInterfaces .contains (method .getDeclaringClass ())) {
309
- return method .invoke (decorated , args );
393
+ return method .invoke (instance , args );
310
394
}
311
395
// Check if the class in which the method resides, implements any one of the
312
396
// interfaces that we extracted from the decorated class.
@@ -317,9 +401,9 @@ protected final <Z> Z createProxy(final Decorated<Z> decorated, Class<Z> clazz)
317
401
eachInterface .isAssignableFrom (method .getDeclaringClass ()));
318
402
319
403
if (isCompatible ) {
320
- decorated .beforeCall (method , args );
321
- Object result = decorated .call (method , args );
322
- decorated .afterCall (method , result , args );
404
+ instance .beforeCall (method , args );
405
+ Object result = instance .call (method , args );
406
+ instance .afterCall (method , result , args );
323
407
return result ;
324
408
}
325
409
@@ -333,19 +417,24 @@ protected final <Z> Z createProxy(final Decorated<Z> decorated, Class<Z> clazz)
333
417
eachInterface .isAssignableFrom (method .getDeclaringClass ()));
334
418
335
419
if (isCompatible ) {
336
- return derivedInterfaces .get (method .getDeclaringClass ()).invoke (proxy , method , args );
420
+ return derivedInterfaces
421
+ .get (method .getDeclaringClass ())
422
+ .apply (instance .getOriginal ())
423
+ .invoke (proxy , method , args );
337
424
}
338
425
339
- return method .invoke (decorated .getOriginal (), args );
426
+ return method .invoke (instance .getOriginal (), args );
340
427
} catch (InvocationTargetException e ) {
341
- return decorated .onError (method , e , args );
428
+ return instance .onError (method , e , args );
342
429
}
343
430
};
344
431
345
432
Set <Class <?>> allInterfaces = new HashSet <>();
346
433
allInterfaces .addAll (decoratedInterfaces );
347
434
allInterfaces .addAll (originalInterfaces );
348
435
allInterfaces .addAll (derivedInterfaces .keySet ());
436
+ // ensure a decorated driver can get decorated again
437
+ allInterfaces .remove (HasTarget .class );
349
438
Class <?>[] allInterfacesArray = allInterfaces .toArray (new Class <?>[0 ]);
350
439
351
440
Class <? extends Z > proxy =
@@ -354,20 +443,15 @@ protected final <Z> Z createProxy(final Decorated<Z> decorated, Class<Z> clazz)
354
443
.implement (allInterfacesArray )
355
444
.method (ElementMatchers .any ())
356
445
.intercept (InvocationHandlerAdapter .of (handler ))
446
+ .defineField ("target" , Decorated .class , Visibility .PRIVATE )
447
+ .implement (HasTarget .class )
448
+ .intercept (FieldAccessor .ofField ("target" ))
357
449
.make ()
358
450
.load (clazz .getClassLoader (), ClassLoadingStrategy .Default .WRAPPER )
359
451
.getLoaded ()
360
452
.asSubclass (clazz );
361
453
362
- try {
363
- return proxy .newInstance ();
364
- } catch (ReflectiveOperationException e ) {
365
- throw new IllegalStateException ("Unable to create new proxy" , e );
366
- }
367
- }
368
-
369
- static Set <Class <?>> extractInterfaces (final Object object ) {
370
- return extractInterfaces (object .getClass ());
454
+ return new ProxyFactory <Z >(proxy );
371
455
}
372
456
373
457
private static Set <Class <?>> extractInterfaces (final Class <?> clazz ) {
@@ -393,43 +477,46 @@ private static void extractInterfaces(final Set<Class<?>> collector, final Class
393
477
extractInterfaces (collector , clazz .getSuperclass ());
394
478
}
395
479
396
- private Map <Class <?>, InvocationHandler > deriveAdditionalInterfaces (Object object ) {
397
- Map <Class <?>, InvocationHandler > handlers = new HashMap <>();
480
+ private < Z > Map <Class <?>, Function < Z , InvocationHandler >> deriveAdditionalInterfaces (Z sample ) {
481
+ Map <Class <?>, Function < Z , InvocationHandler > > handlers = new HashMap <>();
398
482
399
- if (object instanceof WebDriver && !(object instanceof WrapsDriver )) {
483
+ if (sample instanceof WebDriver && !(sample instanceof WrapsDriver )) {
400
484
handlers .put (
401
485
WrapsDriver .class ,
402
- (proxy , method , args ) -> {
403
- if ("getWrappedDriver" .equals (method .getName ())) {
404
- return object ;
405
- }
406
- throw new UnsupportedOperationException (method .getName ());
407
- });
486
+ (instance ) ->
487
+ (proxy , method , args ) -> {
488
+ if ("getWrappedDriver" .equals (method .getName ())) {
489
+ return instance ;
490
+ }
491
+ throw new UnsupportedOperationException (method .getName ());
492
+ });
408
493
}
409
494
410
- if (object instanceof WebElement && !(object instanceof WrapsElement )) {
495
+ if (sample instanceof WebElement && !(sample instanceof WrapsElement )) {
411
496
handlers .put (
412
497
WrapsElement .class ,
413
- (proxy , method , args ) -> {
414
- if ("getWrappedElement" .equals (method .getName ())) {
415
- return object ;
416
- }
417
- throw new UnsupportedOperationException (method .getName ());
418
- });
498
+ (instance ) ->
499
+ (proxy , method , args ) -> {
500
+ if ("getWrappedElement" .equals (method .getName ())) {
501
+ return instance ;
502
+ }
503
+ throw new UnsupportedOperationException (method .getName ());
504
+ });
419
505
}
420
506
421
507
try {
422
- Method toJson = object .getClass ().getDeclaredMethod ("toJson" );
508
+ Method toJson = sample .getClass ().getDeclaredMethod ("toJson" );
423
509
toJson .setAccessible (true );
424
510
425
511
handlers .put (
426
512
JsonSerializer .class ,
427
- ((proxy , method , args ) -> {
428
- if ("toJson" .equals (method .getName ())) {
429
- return toJson .invoke (object );
430
- }
431
- throw new UnsupportedOperationException (method .getName ());
432
- }));
513
+ (instance ) ->
514
+ ((proxy , method , args ) -> {
515
+ if ("toJson" .equals (method .getName ())) {
516
+ return toJson .invoke (instance );
517
+ }
518
+ throw new UnsupportedOperationException (method .getName ());
519
+ }));
433
520
} catch (NoSuchMethodException e ) {
434
521
// Fine. Just fall through
435
522
}
0 commit comments