Skip to content

Commit 785867b

Browse files
committed
runtime generate missing methods in SerializedOfflinePlayer
1 parent 80aa420 commit 785867b

File tree

3 files changed

+64
-5
lines changed

3 files changed

+64
-5
lines changed

src/main/java/com/comphenix/protocol/events/PacketEvent.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ private void writeObject(ObjectOutputStream output) throws IOException {
527527

528528
// Write the name of the player (or NULL if it's not set)
529529
Player player = getPlayer();
530-
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
530+
output.writeObject(player != null ? SerializedOfflinePlayer.init(player) : null);
531531
}
532532

533533
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {

src/main/java/com/comphenix/protocol/events/SerializedOfflinePlayer.java

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,12 @@
5151
import java.lang.reflect.Method;
5252
import java.time.Duration;
5353
import java.time.Instant;
54+
import java.util.ArrayList;
5455
import java.util.Date;
56+
import java.util.HashSet;
57+
import java.util.List;
5558
import java.util.Map;
59+
import java.util.Set;
5660
import java.util.UUID;
5761
import java.util.concurrent.ConcurrentHashMap;
5862

@@ -61,7 +65,7 @@
6165
*
6266
* @author Kristian
6367
*/
64-
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
68+
abstract class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
6569

6670
/**
6771
* Generated by Eclipse.
@@ -84,11 +88,25 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
8488
private long lastSeen;
8589

8690
private static final Constructor<?> proxyPlayerConstructor = setupProxyPlayerConstructor();
91+
private static final Constructor<? extends SerializedOfflinePlayer> CLASS_CONSTRUCTOR = setupClassConstructor();
92+
93+
public static SerializedOfflinePlayer init(OfflinePlayer player) {
94+
try {
95+
CLASS_CONSTRUCTOR.setAccessible(true);
96+
return CLASS_CONSTRUCTOR.newInstance(player);
97+
} catch (IllegalAccessException e) {
98+
throw new RuntimeException("Cannot access reflection.", e);
99+
} catch (InstantiationException e) {
100+
throw new RuntimeException("Cannot instantiate object.", e);
101+
} catch (InvocationTargetException e) {
102+
throw new RuntimeException("Error in invocation.", e);
103+
}
104+
}
87105

88106
/**
89107
* Constructor used by serialization.
90108
*/
91-
public SerializedOfflinePlayer() {
109+
protected SerializedOfflinePlayer() {
92110
// Do nothing
93111
}
94112

@@ -97,7 +115,7 @@ public SerializedOfflinePlayer() {
97115
*
98116
* @param offline - another player.
99117
*/
100-
public SerializedOfflinePlayer(OfflinePlayer offline) {
118+
protected SerializedOfflinePlayer(OfflinePlayer offline) {
101119
this.name = offline.getName();
102120
this.uuid = offline.getUniqueId();
103121
this.firstPlayed = offline.getFirstPlayed();
@@ -342,6 +360,47 @@ public Player getProxyPlayer() {
342360
}
343361
}
344362

363+
private static Constructor<? extends SerializedOfflinePlayer> setupClassConstructor() {
364+
final Method[] existingMethods = SerializedOfflinePlayer.class.getDeclaredMethods();
365+
final Set<String> existingMethodNames = new HashSet<>();
366+
367+
for (int idx = 0; idx < existingMethods.length; idx++) {
368+
existingMethodNames.add(existingMethods[idx].getName());
369+
}
370+
371+
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
372+
final List<String> methodNamesToAdd = new ArrayList<>();
373+
374+
for (int idx = 0; idx < offlinePlayerMethods.length; idx++) {
375+
final String name = offlinePlayerMethods[idx].getName();
376+
377+
if (!existingMethodNames.contains(name)) {
378+
methodNamesToAdd.add(name);
379+
}
380+
}
381+
382+
final ElementMatcher.Junction<ByteCodeElement> missingMethods =
383+
ElementMatchers.namedOneOf(methodNamesToAdd.toArray(new String[methodNamesToAdd.size()]));
384+
385+
final InvocationHandlerAdapter throwException = InvocationHandlerAdapter.of((obj, method, args) -> {
386+
throw new UnsupportedOperationException(
387+
"The method " + method.getName() + " is not supported.");
388+
});
389+
390+
try {
391+
return ByteBuddyFactory.getInstance()
392+
.createSubclass(SerializedOfflinePlayer.class)
393+
.method(missingMethods)
394+
.intercept(throwException)
395+
.make()
396+
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
397+
.getLoaded()
398+
.getConstructor(OfflinePlayer.class);
399+
} catch (NoSuchMethodException ex) {
400+
throw new RuntimeException("Failed to find SerializedOfflinePlayer constructor!", ex);
401+
}
402+
}
403+
345404
private static Constructor<? extends Player> setupProxyPlayerConstructor() {
346405
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
347406
final String[] methodNames = new String[offlinePlayerMethods.length];

src/test/java/com/comphenix/protocol/events/SerializedOfflinePlayerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public void initMocks() {
3939
when(offlinePlayer.hasPlayedBefore()).thenReturn(playedBefore);
4040
when(offlinePlayer.isWhitelisted()).thenReturn(whitelisted);
4141

42-
serializedOfflinePlayer = new SerializedOfflinePlayer(offlinePlayer);
42+
serializedOfflinePlayer = SerializedOfflinePlayer.init(offlinePlayer);
4343
}
4444

4545
@Test

0 commit comments

Comments
 (0)