21
21
import java .io .ObjectInputStream ;
22
22
import java .io .ObjectOutputStream ;
23
23
import java .io .Serializable ;
24
+ import java .lang .reflect .Constructor ;
25
+ import java .lang .reflect .InvocationTargetException ;
24
26
import java .lang .reflect .Method ;
25
27
import java .util .Map ;
26
28
import java .util .UUID ;
27
- import java .util .concurrent .ConcurrentHashMap ;
28
-
29
- import net .sf .cglib .proxy .Enhancer ;
30
- import net .sf .cglib .proxy .MethodInterceptor ;
31
- import net .sf .cglib .proxy .MethodProxy ;
29
+ import java .util .function .Function ;
30
+
31
+ import net .bytebuddy .description .ByteCodeElement ;
32
+ import net .bytebuddy .description .modifier .Visibility ;
33
+ import net .bytebuddy .dynamic .loading .ClassLoadingStrategy ;
34
+ import net .bytebuddy .dynamic .scaffold .subclass .ConstructorStrategy ;
35
+ import net .bytebuddy .implementation .FieldAccessor ;
36
+ import net .bytebuddy .implementation .InvocationHandlerAdapter ;
37
+ import net .bytebuddy .implementation .MethodCall ;
38
+ import net .bytebuddy .implementation .MethodDelegation ;
39
+ import net .bytebuddy .implementation .bind .annotation .FieldValue ;
40
+ import net .bytebuddy .implementation .bind .annotation .Pipe ;
41
+ import net .bytebuddy .implementation .bind .annotation .RuntimeType ;
42
+ import net .bytebuddy .matcher .ElementMatcher ;
43
+ import net .bytebuddy .matcher .ElementMatchers ;
32
44
33
45
import org .bukkit .*;
34
46
import org .bukkit .entity .EntityType ;
35
47
import org .bukkit .entity .Player ;
36
48
37
- import com .comphenix .protocol .utility .EnhancerFactory ;
49
+ import com .comphenix .protocol .utility .ByteBuddyFactory ;
38
50
39
51
/**
40
52
* Represents a player object that can be serialized by Java.
@@ -60,9 +72,8 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
60
72
private boolean playedBefore ;
61
73
private boolean online ;
62
74
private boolean whitelisted ;
63
-
64
- // Proxy helper
65
- private static Map <String , Method > lookup = new ConcurrentHashMap <String , Method >();
75
+
76
+ private static final Constructor <?> proxyPlayerConstructor = setupProxyPlayerConstructor ();
66
77
67
78
/**
68
79
* Constructor used by serialization.
@@ -243,42 +254,78 @@ public Player getPlayer() {
243
254
}
244
255
245
256
/**
246
- * Retrieve a player object that implements OfflinePlayer by refering to this object.
257
+ * Retrieve a player object that implements OfflinePlayer by referring to this object.
247
258
* <p>
248
259
* All other methods cause an exception.
249
260
* @return Proxy object.
250
261
*/
251
262
public Player getProxyPlayer () {
252
-
253
- // Remember to initialize the method filter
254
- if (lookup .size () == 0 ) {
255
- // Add all public methods
256
- for (Method method : OfflinePlayer .class .getMethods ()) {
257
- lookup .put (method .getName (), method );
258
- }
263
+ try {
264
+ return (Player ) proxyPlayerConstructor .newInstance (this );
265
+ } catch (IllegalAccessException e ) {
266
+ throw new RuntimeException ("Cannot access reflection." , e );
267
+ } catch (InstantiationException e ) {
268
+ throw new RuntimeException ("Cannot instantiate object." , e );
269
+ } catch (InvocationTargetException e ) {
270
+ throw new RuntimeException ("Error in invocation." , e );
259
271
}
260
-
261
- // MORE CGLIB magic!
262
- Enhancer ex = EnhancerFactory .getInstance ().createEnhancer ();
263
- ex .setSuperclass (Player .class );
264
- ex .setCallback (new MethodInterceptor () {
265
- @ Override
266
- public Object intercept (Object obj , Method method , Object [] args , MethodProxy proxy ) throws Throwable {
267
-
268
- // There's no overloaded methods, so we don't care
269
- Method offlineMethod = lookup .get (method .getName ());
270
-
271
- // Ignore all other methods
272
- if (offlineMethod == null ) {
272
+ }
273
+
274
+ private static Constructor <? extends Player > setupProxyPlayerConstructor ()
275
+ {
276
+ final Method [] offlinePlayerMethods = OfflinePlayer .class .getMethods ();
277
+ final String [] methodNames = new String [offlinePlayerMethods .length ];
278
+ for (int idx = 0 ; idx < offlinePlayerMethods .length ; ++idx )
279
+ methodNames [idx ] = offlinePlayerMethods [idx ].getName ();
280
+
281
+ final ElementMatcher .Junction <ByteCodeElement > forwardedMethods = ElementMatchers .namedOneOf (methodNames );
282
+
283
+ try {
284
+ final MethodDelegation forwarding = MethodDelegation .withDefaultConfiguration ()
285
+ .withBinders (Pipe .Binder .install (Function .class ))
286
+ .to (new Object () {
287
+ @ RuntimeType
288
+ public Object intercept (@ Pipe Function <OfflinePlayer , Object > pipe ,
289
+ @ FieldValue ("offlinePlayer" ) OfflinePlayer proxy ) {
290
+ return pipe .apply (proxy );
291
+ }
292
+ });
293
+
294
+ final InvocationHandlerAdapter throwException = InvocationHandlerAdapter .of ((obj , method , args ) -> {
273
295
throw new UnsupportedOperationException (
274
- "The method " + method .getName () + " is not supported for offline players." );
275
- }
276
-
277
- // Invoke our on method
278
- return offlineMethod .invoke (SerializedOfflinePlayer .this , args );
279
- }
280
- });
281
-
282
- return (Player ) ex .create ();
296
+ "The method " + method .getName () + " is not supported for offline players." );
297
+ });
298
+
299
+ return ByteBuddyFactory .getInstance ()
300
+ .createSubclass (PlayerUnion .class , ConstructorStrategy .Default .NO_CONSTRUCTORS )
301
+ .name (SerializedOfflinePlayer .class .getPackage ().getName () + ".PlayerInvocationHandler" )
302
+
303
+ .defineField ("offlinePlayer" , OfflinePlayer .class , Visibility .PRIVATE )
304
+ .defineConstructor (Visibility .PUBLIC )
305
+ .withParameters (OfflinePlayer .class )
306
+ .intercept (MethodCall .invoke (Object .class .getDeclaredConstructor ())
307
+ .andThen (FieldAccessor .ofField ("offlinePlayer" ).setsArgumentAt (0 )))
308
+
309
+ .method (forwardedMethods )
310
+ .intercept (forwarding )
311
+
312
+ .method (ElementMatchers .not (forwardedMethods ))
313
+ .intercept (throwException )
314
+
315
+ .make ()
316
+ .load (ByteBuddyFactory .getInstance ().getClassLoader (), ClassLoadingStrategy .Default .INJECTION )
317
+ .getLoaded ()
318
+ .getDeclaredConstructor (OfflinePlayer .class );
319
+ } catch (NoSuchMethodException e ) {
320
+ throw new RuntimeException ("Failed to find Player constructor!" , e );
321
+ }
322
+ }
323
+
324
+ /**
325
+ * This interface extends both OfflinePlayer and Player (in that order) so that the class generated by ByteBuddy
326
+ * looks at OfflinePlayer's methods first while still being a Player.
327
+ */
328
+ private interface PlayerUnion extends OfflinePlayer , Player
329
+ {
283
330
}
284
331
}
0 commit comments