Move legacy Serializable usage into a subclass.

This commit is contained in:
t00thpick1 2021-01-02 16:46:35 -05:00
parent d08c9391b0
commit 10694042e9

View File

@ -3,33 +3,35 @@ package com.gmail.nossr50.util.blockmeta;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*; import java.io.*;
import java.util.BitSet; import java.util.BitSet;
import java.util.UUID; import java.util.UUID;
public class BitSetChunkStore implements ChunkStore, Serializable { public class BitSetChunkStore implements ChunkStore {
private static final long serialVersionUID = -1L;
transient private boolean dirty = false;
// Bitset store conforms to a "bottom-up" bit ordering consisting of a stack of {worldHeight} Y planes, each Y plane consists of 16 Z rows of 16 X bits.
private BitSet store;
private static final int CURRENT_VERSION = 8; private static final int CURRENT_VERSION = 8;
private static final int MAGIC_NUMBER = 0xEA5EDEBB; private static final int MAGIC_NUMBER = 0xEA5EDEBB;
private int cx;
private int cz; private final int cx;
private int worldHeight; private final int cz;
private UUID worldUid; private final int worldHeight;
private final UUID worldUid;
// Bitset store conforms to a "bottom-up" bit ordering consisting of a stack of {worldHeight} Y planes, each Y plane consists of 16 Z rows of 16 X bits.
private final BitSet store;
private transient boolean dirty = false;
public BitSetChunkStore(@NotNull World world, int cx, int cz) { public BitSetChunkStore(@NotNull World world, int cx, int cz) {
this.cx = cx; this(world.getUID(), world.getMaxHeight(), cx, cz);
this.cz = cz;
this.worldUid = world.getUID();
this.worldHeight = world.getMaxHeight();
this.store = new BitSet(16 * 16 * worldHeight);
} }
private BitSetChunkStore() {} private BitSetChunkStore(@NotNull UUID worldUid, int worldHeight, int cx, int cz) {
this.cx = cx;
this.cz = cz;
this.worldUid = worldUid;
this.worldHeight = worldHeight;
this.store = new BitSet(16 * 16 * worldHeight);
}
@Override @Override
public boolean isDirty() { public boolean isDirty() {
@ -83,58 +85,24 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
} }
private int coordToIndex(int x, int y, int z) { private int coordToIndex(int x, int y, int z) {
return coordToIndex(x, y, z, worldHeight);
}
private static int coordToIndex(int x, int y, int z, int worldHeight) {
if (x < 0 || x >= 16 || y < 0 || y >= worldHeight || z < 0 || z >= 16) if (x < 0 || x >= 16 || y < 0 || y >= worldHeight || z < 0 || z >= 16)
throw new IndexOutOfBoundsException(String.format("x: %d y: %d z: %d World Height: %d", x, y, z, worldHeight)); throw new IndexOutOfBoundsException(String.format("x: %d y: %d z: %d World Height: %d", x, y, z, worldHeight));
return (z * 16 + x) + (256 * y); return (z * 16 + x) + (256 * y);
} }
private void fixWorldHeight() { private static int getWorldHeight(UUID worldUid, int storedWorldHeight)
{
World world = Bukkit.getWorld(worldUid); World world = Bukkit.getWorld(worldUid);
// Not sure how this case could come up, but might as well handle it gracefully. Loading a chunkstore for an unloaded world? // Not sure how this case could come up, but might as well handle it gracefully. Loading a chunkstore for an unloaded world?
if (world == null) if (world == null)
return; return storedWorldHeight;
// Lop off any extra data if the world height has shrunk return world.getMaxHeight();
int currentWorldHeight = world.getMaxHeight();
if (currentWorldHeight < worldHeight)
{
store.clear(coordToIndex(16, currentWorldHeight, 16), store.length());
worldHeight = currentWorldHeight;
dirty = true;
}
// If the world height has grown, update the worldHeight variable, but don't bother marking it dirty as unless something else changes we don't need to force a file write;
else if (currentWorldHeight > worldHeight)
worldHeight = currentWorldHeight;
}
@Deprecated
private void writeObject(ObjectOutputStream out) throws IOException {
throw new UnsupportedOperationException("Serializable support should only be used for legacy deserialization");
}
@Deprecated
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.readInt(); // Magic number
in.readInt(); // Format version
long lsb = in.readLong();
long msb = in.readLong();
worldUid = new UUID(msb, lsb);
cx = in.readInt();
cz = in.readInt();
boolean[][][] oldStore = (boolean[][][]) in.readObject();
worldHeight = oldStore[0][0].length;
store = new BitSet(16 * 16 * worldHeight / 8);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < worldHeight; y++) {
store.set(coordToIndex(x, y, z), oldStore[x][z][y]);
}
}
}
dirty = true;
fixWorldHeight();
} }
private void serialize(DataOutputStream out) throws IOException { private void serialize(DataOutputStream out) throws IOException {
@ -163,26 +131,34 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION) if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION)
throw new IOException(); throw new IOException();
BitSetChunkStore chunkStore = new BitSetChunkStore();
long lsb = in.readLong(); long lsb = in.readLong();
long msb = in.readLong(); long msb = in.readLong();
chunkStore.worldUid = new UUID(msb, lsb); UUID worldUid = new UUID(msb, lsb);
chunkStore.cx = in.readInt(); int cx = in.readInt();
chunkStore.cz = in.readInt(); int cz = in.readInt();
chunkStore.worldHeight = in.readInt(); int worldHeight = in.readInt();
byte[] temp = new byte[in.readInt()]; byte[] temp = new byte[in.readInt()];
in.readFully(temp); in.readFully(temp);
chunkStore.store = BitSet.valueOf(temp); BitSet stored = BitSet.valueOf(temp);
int currentWorldHeight = getWorldHeight(worldUid, worldHeight);
boolean worldHeightShrunk = currentWorldHeight < worldHeight;
// Lop off extra data if world height has shrunk
if (worldHeightShrunk)
stored.clear(coordToIndex(16, currentWorldHeight, 16, worldHeight), stored.length());
BitSetChunkStore chunkStore = new BitSetChunkStore(worldUid, currentWorldHeight, cx, cz);
chunkStore.store.or(stored);
chunkStore.dirty = worldHeightShrunk; // In the expanded case there is no reason to re-write it unless the data changes
chunkStore.fixWorldHeight();
return chunkStore; return chunkStore;
} }
public static class Serialization { public static class Serialization {
public static final short STREAM_MAGIC = (short)0xACDC; public static final short STREAM_MAGIC = (short)0xACDC; // Rock on
public static @NotNull ChunkStore readChunkStore(DataInputStream inputStream) throws IOException { public static @NotNull ChunkStore readChunkStore(DataInputStream inputStream) throws IOException {
if (inputStream.markSupported()) if (inputStream.markSupported())
@ -198,7 +174,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
{ {
// Creates a new stream with the two magic number bytes and then the rest of the original stream... Java is so dumb. I just wanted to look at two bytes. // Creates a new stream with the two magic number bytes and then the rest of the original stream... Java is so dumb. I just wanted to look at two bytes.
PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream, 2); PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream, 2);
pushbackInputStream.unread((magicNumber >>> 0) & 0xFF); pushbackInputStream.unread((magicNumber) & 0xFF);
pushbackInputStream.unread((magicNumber >>> 8) & 0xFF); pushbackInputStream.unread((magicNumber >>> 8) & 0xFF);
inputStream = new DataInputStream(pushbackInputStream); inputStream = new DataInputStream(pushbackInputStream);
} }
@ -218,8 +194,61 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
((BitSetChunkStore)chunkStore).serialize(outputStream); ((BitSetChunkStore)chunkStore).serialize(outputStream);
} }
// Handles loading the old serialized classes even though we have changed name/package // Handles loading the old serialized class
private static class LegacyDeserializationInputStream extends ObjectInputStream { private static class LegacyDeserializationInputStream extends ObjectInputStream {
private static class LegacyChunkStoreDeserializer implements Serializable
{
private static final long serialVersionUID = -1L;
private int cx;
private int cz;
private int worldHeight;
private UUID worldUid;
private boolean[][][] store;
private LegacyChunkStoreDeserializer() {}
@Deprecated
private void writeObject(ObjectOutputStream out) throws IOException {
throw new UnsupportedOperationException("You goofed.");
}
@Deprecated
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.readInt(); // Magic number
in.readInt(); // Format version
long lsb = in.readLong();
long msb = in.readLong();
worldUid = new UUID(msb, lsb);
cx = in.readInt();
cz = in.readInt();
store = (boolean[][][]) in.readObject();
worldHeight = store[0][0].length;
}
public BitSetChunkStore convert()
{
int currentWorldHeight = getWorldHeight(worldUid, worldHeight);
BitSetChunkStore converted = new BitSetChunkStore(worldUid, currentWorldHeight, cx, cz);
// Read old data into new chunkstore
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < worldHeight && y < currentWorldHeight; y++) {
converted.store.set(converted.coordToIndex(x, y, z), store[x][z][y]);
}
}
}
// Mark dirty so it will be re-written in new format on close
converted.dirty = true;
return converted;
}
}
public LegacyDeserializationInputStream(InputStream in) throws IOException { public LegacyDeserializationInputStream(InputStream in) throws IOException {
super(in); super(in);
enableResolveObject(true); enableResolveObject(true);
@ -229,13 +258,14 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass read = super.readClassDescriptor(); ObjectStreamClass read = super.readClassDescriptor();
if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore")) if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore"))
return ObjectStreamClass.lookup(BitSetChunkStore.class); return ObjectStreamClass.lookup(LegacyChunkStoreDeserializer.class);
return read; return read;
} }
public ChunkStore readLegacyChunkStore(){ public ChunkStore readLegacyChunkStore(){
try { try {
return (ChunkStore) readObject(); LegacyChunkStoreDeserializer deserializer = (LegacyChunkStoreDeserializer)readObject();
return deserializer.convert();
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
return null; return null;
} }