Compare commits

...

3 Commits

Author SHA1 Message Date
quang 95f2a0f3a2
Merge cd45943cdd into e1255edb32 2024-04-08 03:38:27 +09:00
Dan Mulloy e1255edb32
Fix build 2024-04-07 11:18:58 -05:00
Trần Nguyễn Ngọc Quang cd45943cdd
generate missing methods in SerializedOfflinePlayer at runtime 2024-02-02 03:21:53 +07:00
3 changed files with 82 additions and 5 deletions

View File

@ -527,7 +527,7 @@ public class PacketEvent extends EventObject implements Cancellable {
// Write the name of the player (or NULL if it's not set)
Player player = getPlayer();
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
output.writeObject(player != null ? SerializedOfflinePlayer.init(player) : null);
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {

View File

@ -51,8 +51,12 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@ -61,7 +65,7 @@ import java.util.concurrent.ConcurrentHashMap;
*
* @author Kristian
*/
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
abstract class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
/**
* Generated by Eclipse.
@ -84,11 +88,33 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
private long lastSeen;
private static final Constructor<?> proxyPlayerConstructor = setupProxyPlayerConstructor();
private static final Constructor<? extends SerializedOfflinePlayer> CLASS_CONSTRUCTOR = setupClassConstructor();
/**
* Initialize a serializable offline player object from another offline player.
* <p>
* All other methods cause an exception.
*
* @param player - another offline player.
* @return A serializable offline player object.
*/
public static SerializedOfflinePlayer init(OfflinePlayer player) {
try {
CLASS_CONSTRUCTOR.setAccessible(true);
return CLASS_CONSTRUCTOR.newInstance(player);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot instantiate object.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error in invocation.", e);
}
}
/**
* Constructor used by serialization.
*/
public SerializedOfflinePlayer() {
protected SerializedOfflinePlayer() {
// Do nothing
}
@ -97,7 +123,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
*
* @param offline - another player.
*/
public SerializedOfflinePlayer(OfflinePlayer offline) {
protected SerializedOfflinePlayer(OfflinePlayer offline) {
this.name = offline.getName();
this.uuid = offline.getUniqueId();
this.firstPlayed = offline.getFirstPlayed();
@ -145,6 +171,16 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return lastSeen;
}
@Override
public Location getRespawnLocation() {
return null;
}
@Override
public Location getLocation() {
return null;
}
// TODO do we need to implement this?
public void incrementStatistic(Statistic statistic) throws IllegalArgumentException {
@ -342,6 +378,47 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}
}
private static Constructor<? extends SerializedOfflinePlayer> setupClassConstructor() {
final Method[] existingMethods = SerializedOfflinePlayer.class.getDeclaredMethods();
final Set<String> existingMethodNames = new HashSet<>();
for (int idx = 0; idx < existingMethods.length; idx++) {
existingMethodNames.add(existingMethods[idx].getName());
}
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
final List<String> methodNamesToAdd = new ArrayList<>();
for (int idx = 0; idx < offlinePlayerMethods.length; idx++) {
final String name = offlinePlayerMethods[idx].getName();
if (!existingMethodNames.contains(name)) {
methodNamesToAdd.add(name);
}
}
final ElementMatcher.Junction<ByteCodeElement> missingMethods =
ElementMatchers.namedOneOf(methodNamesToAdd.toArray(new String[methodNamesToAdd.size()]));
final InvocationHandlerAdapter throwException = InvocationHandlerAdapter.of((obj, method, args) -> {
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported.");
});
try {
return ByteBuddyFactory.getInstance()
.createSubclass(SerializedOfflinePlayer.class)
.method(missingMethods)
.intercept(throwException)
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getConstructor(OfflinePlayer.class);
} catch (NoSuchMethodException ex) {
throw new RuntimeException("Failed to find SerializedOfflinePlayer constructor!", ex);
}
}
private static Constructor<? extends Player> setupProxyPlayerConstructor() {
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
final String[] methodNames = new String[offlinePlayerMethods.length];

View File

@ -39,7 +39,7 @@ public class SerializedOfflinePlayerTest {
when(offlinePlayer.hasPlayedBefore()).thenReturn(playedBefore);
when(offlinePlayer.isWhitelisted()).thenReturn(whitelisted);
serializedOfflinePlayer = new SerializedOfflinePlayer(offlinePlayer);
serializedOfflinePlayer = SerializedOfflinePlayer.init(offlinePlayer);
}
@Test