Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ private void writeObject(ObjectOutputStream output) throws IOException {

// 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,12 @@
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;

Expand All @@ -61,7 +65,7 @@
*
* @author Kristian
*/
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
abstract class SerializedOfflinePlayer implements OfflinePlayer, Serializable {

/**
* Generated by Eclipse.
Expand All @@ -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
}

Expand All @@ -97,7 +123,7 @@ public SerializedOfflinePlayer() {
*
* @param offline - another player.
*/
public SerializedOfflinePlayer(OfflinePlayer offline) {
protected SerializedOfflinePlayer(OfflinePlayer offline) {
this.name = offline.getName();
this.uuid = offline.getUniqueId();
this.firstPlayed = offline.getFirstPlayed();
Expand Down Expand Up @@ -342,6 +368,47 @@ public Player getProxyPlayer() {
}
}

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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void initMocks() {
when(offlinePlayer.hasPlayedBefore()).thenReturn(playedBefore);
when(offlinePlayer.isWhitelisted()).thenReturn(whitelisted);

serializedOfflinePlayer = new SerializedOfflinePlayer(offlinePlayer);
serializedOfflinePlayer = SerializedOfflinePlayer.init(offlinePlayer);
}

@Test
Expand Down