Browse Source

Move legacy Serializable usage into a subclass.

t00thpick1 4 years ago
parent
commit
10694042e9
1 changed files with 99 additions and 69 deletions
  1. 99 69
      src/main/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStore.java

+ 99 - 69
src/main/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStore.java

@@ -3,34 +3,36 @@ package com.gmail.nossr50.util.blockmeta;
 import org.bukkit.Bukkit;
 import org.bukkit.World;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 import java.io.*;
 import java.util.BitSet;
 import java.util.UUID;
 
-public class BitSetChunkStore implements ChunkStore, Serializable {
-    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;
+public class BitSetChunkStore implements ChunkStore {
     private static final int CURRENT_VERSION = 8;
     private static final int MAGIC_NUMBER = 0xEA5EDEBB;
-    private int cx;
-    private int cz;
-    private int worldHeight;
-    private UUID worldUid;
+
+    private final int cx;
+    private final int cz;
+    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) {
+        this(world.getUID(), world.getMaxHeight(), cx, cz);
+    }
+
+    private BitSetChunkStore(@NotNull UUID worldUid, int worldHeight, int cx, int cz) {
         this.cx = cx;
         this.cz = cz;
-        this.worldUid = world.getUID();
-        this.worldHeight = world.getMaxHeight();
+        this.worldUid = worldUid;
+        this.worldHeight = worldHeight;
         this.store = new BitSet(16 * 16 * worldHeight);
     }
 
-    private BitSetChunkStore() {}
-
     @Override
     public boolean isDirty() {
         return dirty;
@@ -83,58 +85,24 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
     }
 
     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)
             throw new IndexOutOfBoundsException(String.format("x: %d y: %d z: %d World Height: %d", x, y, z, worldHeight));
         return (z * 16 + x) + (256 * y);
     }
 
-    private void fixWorldHeight() {
+    private static int getWorldHeight(UUID worldUid, int storedWorldHeight)
+    {
         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?
         if (world == null)
-            return;
-
-        // Lop off any extra data if the world height has shrunk
-        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");
-    }
+            return storedWorldHeight;
 
-    @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();
+        return world.getMaxHeight();
     }
 
     private void serialize(DataOutputStream out) throws IOException {
@@ -163,26 +131,34 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
         if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION)
             throw new IOException();
 
-        BitSetChunkStore chunkStore = new BitSetChunkStore();
-
         long lsb = in.readLong();
         long msb = in.readLong();
-        chunkStore.worldUid = new UUID(msb, lsb);
-        chunkStore.cx = in.readInt();
-        chunkStore.cz = in.readInt();
+        UUID worldUid = new UUID(msb, lsb);
+        int cx = in.readInt();
+        int cz = in.readInt();
 
-        chunkStore.worldHeight = in.readInt();
+        int worldHeight = in.readInt();
         byte[] temp = new byte[in.readInt()];
         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;
     }
 
     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 {
             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.
                     PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream, 2);
-                    pushbackInputStream.unread((magicNumber >>> 0) & 0xFF);
+                    pushbackInputStream.unread((magicNumber) & 0xFF);
                     pushbackInputStream.unread((magicNumber >>> 8) & 0xFF);
                     inputStream = new DataInputStream(pushbackInputStream);
                 }
@@ -218,8 +194,61 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
             ((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 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 {
                 super(in);
                 enableResolveObject(true);
@@ -229,13 +258,14 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
             protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
                 ObjectStreamClass read = super.readClassDescriptor();
                 if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore"))
-                    return ObjectStreamClass.lookup(BitSetChunkStore.class);
+                    return ObjectStreamClass.lookup(LegacyChunkStoreDeserializer.class);
                 return read;
             }
 
             public ChunkStore readLegacyChunkStore(){
                 try {
-                    return (ChunkStore) readObject();
+                    LegacyChunkStoreDeserializer deserializer = (LegacyChunkStoreDeserializer)readObject();
+                    return deserializer.convert();
                 } catch (IOException | ClassNotFoundException e) {
                     return null;
                 }