Skip to content

Commit cd45943

Browse files
committed
generate missing methods in SerializedOfflinePlayer at runtime
1 parent 80aa420 commit cd45943

File tree

3 files changed

+72
-5
lines changed

3 files changed

+72
-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: 70 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,33 @@ 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+
/**
94+
* Initialize a serializable offline player object from another offline player.
95+
* <p>
96+
* All other methods cause an exception.
97+
*
98+
* @param player - another offline player.
99+
* @return A serializable offline player object.
100+
*/
101+
public static SerializedOfflinePlayer init(OfflinePlayer player) {
102+
try {
103+
CLASS_CONSTRUCTOR.setAccessible(true);
104+
return CLASS_CONSTRUCTOR.newInstance(player);
105+
} catch (IllegalAccessException e) {
106+
throw new RuntimeException("Cannot access reflection.", e);
107+
} catch (InstantiationException e) {
108+
throw new RuntimeException("Cannot instantiate object.", e);
109+
} catch (InvocationTargetException e) {
110+
throw new RuntimeException("Error in invocation.", e);
111+
}
112+
}
87113

88114
/**
89115
* Constructor used by serialization.
90116
*/
91-
public SerializedOfflinePlayer() {
117+
protected SerializedOfflinePlayer() {
92118
// Do nothing
93119
}
94120

@@ -97,7 +123,7 @@ public SerializedOfflinePlayer() {
97123
*
98124
* @param offline - another player.
99125
*/
100-
public SerializedOfflinePlayer(OfflinePlayer offline) {
126+
protected SerializedOfflinePlayer(OfflinePlayer offline) {
101127
this.name = offline.getName();
102128
this.uuid = offline.getUniqueId();
103129
this.firstPlayed = offline.getFirstPlayed();
@@ -342,6 +368,47 @@ public Player getProxyPlayer() {
342368
}
343369
}
344370

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