Skip to content

Commit

Permalink
Added the ability to store persistent data for islands and players (#…
Browse files Browse the repository at this point in the history
…1142)

* Added persistent data container

* Added persistent data containers to islands and players
  • Loading branch information
OmerBenGera committed May 21, 2022
1 parent de6d726 commit 2e354d3
Show file tree
Hide file tree
Showing 35 changed files with 1,059 additions and 9 deletions.
Expand Up @@ -4,6 +4,7 @@
import com.bgsoftware.superiorskyblock.api.island.algorithms.IslandBlocksTrackerAlgorithm;
import com.bgsoftware.superiorskyblock.api.island.algorithms.IslandCalculationAlgorithm;
import com.bgsoftware.superiorskyblock.api.island.algorithms.IslandEntitiesTrackerAlgorithm;
import com.bgsoftware.superiorskyblock.api.persistence.PersistentDataContainer;

public interface IslandsFactory {

Expand Down Expand Up @@ -71,4 +72,12 @@ default IslandEntitiesTrackerAlgorithm createIslandEntitiesTrackerAlgorithm(Isla
*/
IslandEntitiesTrackerAlgorithm createIslandEntitiesTrackerAlgorithm(Island island, IslandEntitiesTrackerAlgorithm original);

/**
* Create a new persistent data container for an island.
*
* @param island The island to create the container for.
* @param original The original persistent data container that was created.
*/
PersistentDataContainer createPersistentDataContainer(Island island, PersistentDataContainer original);

}
@@ -1,5 +1,6 @@
package com.bgsoftware.superiorskyblock.api.factory;

import com.bgsoftware.superiorskyblock.api.persistence.PersistentDataContainer;
import com.bgsoftware.superiorskyblock.api.player.algorithm.PlayerTeleportAlgorithm;
import com.bgsoftware.superiorskyblock.api.wrappers.SuperiorPlayer;

Expand Down Expand Up @@ -31,4 +32,12 @@ default PlayerTeleportAlgorithm createPlayerTeleportAlgorithm(SuperiorPlayer sup
*/
PlayerTeleportAlgorithm createPlayerTeleportAlgorithm(SuperiorPlayer superiorPlayer, PlayerTeleportAlgorithm original);

/**
* Create a new persistent data container for a player.
*
* @param superiorPlayer The player to create the container for.
* @param original The original persistent data container that was created.
*/
PersistentDataContainer createPersistentDataContainer(SuperiorPlayer superiorPlayer, PersistentDataContainer original);

}
Expand Up @@ -11,6 +11,7 @@
import com.bgsoftware.superiorskyblock.api.key.Key;
import com.bgsoftware.superiorskyblock.api.missions.IMissionsHolder;
import com.bgsoftware.superiorskyblock.api.objects.Pair;
import com.bgsoftware.superiorskyblock.api.persistence.IPersistentDataHolder;
import com.bgsoftware.superiorskyblock.api.upgrades.Upgrade;
import com.bgsoftware.superiorskyblock.api.upgrades.UpgradeLevel;
import com.bgsoftware.superiorskyblock.api.wrappers.SuperiorPlayer;
Expand All @@ -32,7 +33,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

public interface Island extends Comparable<Island>, IMissionsHolder, IDatabaseBridgeHolder {
public interface Island extends Comparable<Island>, IMissionsHolder, IPersistentDataHolder, IDatabaseBridgeHolder {

/*
* General methods
Expand Down
@@ -0,0 +1,13 @@
package com.bgsoftware.superiorskyblock.api.persistence;

/**
* Represents an object that can store custom persistent data.
*/
public interface IPersistentDataHolder {

/**
* Get the container of custom persistent data of this object.
*/
PersistentDataContainer getPersistentDataContainer();

}
@@ -0,0 +1,138 @@
package com.bgsoftware.superiorskyblock.api.persistence;

import org.apache.commons.lang.IllegalClassException;

import javax.annotation.Nullable;

public interface PersistentDataContainer {

/**
* Check if the provided key has a matching metadata value.
*
* @param key The key to check.
*/
boolean has(String key);

/**
* Check if the provided key has a matching metadata value of the provided type.
*
* @param key The key to check.
* @param type The type to check.
*/
<T> boolean hasKeyOfType(String key, PersistentDataType<T> type);

/**
* Store a metadata value matching the provided key and type.
*
* @param key The key to store.
* @param type The type of the metadata value.
* @param value The metadata value to store.
* @return The old metadata value that was stored matching the key, if exists.
* @throws IllegalClassException If the old metadata value is not of type {@param type}.
* @throws IllegalStateException If {@param type} doesn't have a valid serializer available.
*/
@Nullable
<T> T put(String key, PersistentDataType<T> type, T value) throws IllegalClassException, IllegalStateException;

/**
* Store a metadata value matching the provided key and type.
*
* @param key The key to store.
* @param type The type of the metadata.
* @param value The metadata value to store.
* @param returnType The type of the old metadata value.
* @return The old metadata value that was stored matching the key, if exists.
* @throws IllegalClassException If the old metadata value is not of type {@param returnType}.
* @throws IllegalStateException If {@param type} doesn't have a valid serializer available.
*/
@Nullable
<T, R> R put(String key, PersistentDataType<T> type, T value, PersistentDataType<R> returnType) throws IllegalClassException, IllegalStateException;

/**
* Remove a metadata value matching the provided key.
*
* @param key The key to remove.
* @return The old metadata value that was stored matching the key, if exists.
*/
@Nullable
Object remove(String key);

/**
* Remove a metadata value matching the provided key and type.
* If the metadata value doesn't match the {@param type}, it will not get removed.
*
* @param key The key to remove.
* @param type The type of the metadata value to remove.
* @return The old metadata value that was stored matching the key, if exists.
* If the metadata value does not match the current type, null will be returned.
*/
@Nullable
<T> T removeKeyOfType(String key, PersistentDataType<T> type);

/**
* Get a metadata value matching the provided key and type.
*
* @param key The key to fetch.
* @param type The type of the metadata value to fetch.
* @return The metadata value that is stored matching the key, if exists.
* @throws IllegalClassException If the metadata value is not of type {@param type}.
*/
@Nullable
<T> T get(String key, PersistentDataType<T> type) throws IllegalClassException;

/**
* Get a metadata value matching the provided key.
*
* @param key The key to fetch.
* @return The metadata value that is stored matching the key, if exists.
*/
@Nullable
Object get(String key);

/**
* Get a metadata value matching the provided key and type.
*
* @param key The key to fetch.
* @param type The type of the metadata value to fetch.
* @param def Value to return in case there is no metadata value matching the provided key.
* @return The metadata value that is stored matching the key, or {@param def} otherwise.
* @throws IllegalClassException If the metadata value is not of type {@param type}.
*/
<T> T getOrDefault(String key, PersistentDataType<T> type, T def) throws IllegalClassException;

/**
* Get a metadata value matching the provided key.
*
* @param key The key to fetch.
* @param def Value to return in case there is no metadata value matching the provided key.
* @return The metadata value that is stored matching the key, or {@param def} otherwise.
*/
Object getOrDefault(String key, Object def);

/**
* Check whether the container is empty.
*/
boolean isEmpty();

/**
* Get the size of the container.
*/
int size();

/**
* Get the serialized contents of the container as a bytes array.
* The format of the serialized data may be different depending on the implementation of the container.
* The serialized data must be loaded without any errors using {@link #load(byte[])}.
*/
byte[] serialize();

/**
* Load contents from the serialized data into the container.
* The format of the serialized data may be different depending on the implementation of the container.
*
* @param data The serialized data.
* @throws IllegalArgumentException If the given data cannot be serialized correctly.
*/
void load(byte[] data) throws IllegalArgumentException;

}
@@ -0,0 +1,61 @@
package com.bgsoftware.superiorskyblock.api.persistence;

import com.google.common.base.Preconditions;

import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.util.UUID;

/**
* Represents a data type that can be stored inside a {@link PersistentDataContainer}.
*
* @param <T> The type of the value to store.
*/
public final class PersistentDataType<T> {

public static final PersistentDataType<BigDecimal> BIG_DECIMAL = new PersistentDataType<>(BigDecimal.class);
public static final PersistentDataType<byte[]> BYTE_ARRAY = new PersistentDataType<>(byte[].class);
public static final PersistentDataType<Byte> BYTE = new PersistentDataType<>(Byte.class);
public static final PersistentDataType<Double> DOUBLE = new PersistentDataType<>(Double.class);
public static final PersistentDataType<Float> FLOAT = new PersistentDataType<>(Float.class);
public static final PersistentDataType<int[]> INT_ARRAY = new PersistentDataType<>(int[].class);
public static final PersistentDataType<Integer> INTEGER = new PersistentDataType<>(Integer.class);
public static final PersistentDataType<Long> LONG = new PersistentDataType<>(Long.class);
public static final PersistentDataType<Short> SHORT = new PersistentDataType<>(Short.class);
public static final PersistentDataType<String> STRING = new PersistentDataType<>(String.class);
public static final PersistentDataType<UUID> UUID = new PersistentDataType<>(UUID.class);

private final Class<T> type;
private final PersistentDataTypeContext<T> context;

/**
* Custom type constructor.
*
* @param type The type.
* @param context The context class used to serialize and deserialize this data type.
*/
public PersistentDataType(Class<T> type, PersistentDataTypeContext<T> context) {
this.type = Preconditions.checkNotNull(type, "type parameter cannot be null");
this.context = Preconditions.checkNotNull(context, "context parameter cannot be null");
}

/**
* Built-in type constructor.
*
* @param type The type.
*/
private PersistentDataType(Class<T> type) {
this.type = type;
this.context = null;
}

public Class<T> getType() {
return this.type;
}

@Nullable
public PersistentDataTypeContext<T> getContext() {
return this.context;
}

}
@@ -0,0 +1,26 @@
package com.bgsoftware.superiorskyblock.api.persistence;

/**
* The context class is used to serialize and deserialize a custom data type.
*
* @param <T> The type of the data value.
*/
public interface PersistentDataTypeContext<T> {

/**
* Serialize the provided value into a bytes buffer.
*
* @param value The value to serialize.
* @return The serialized data.
*/
byte[] serialize(T value);

/**
* Deserialize the provided bytes buffer into a valid value.
*
* @param data The serialized data.
* @return The deserialized value.
*/
T deserialize(byte[] data);

}
Expand Up @@ -7,6 +7,7 @@
import com.bgsoftware.superiorskyblock.api.island.IslandPrivilege;
import com.bgsoftware.superiorskyblock.api.island.PlayerRole;
import com.bgsoftware.superiorskyblock.api.missions.IMissionsHolder;
import com.bgsoftware.superiorskyblock.api.persistence.IPersistentDataHolder;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
Expand All @@ -19,7 +20,7 @@
import java.util.UUID;
import java.util.function.Consumer;

public interface SuperiorPlayer extends IMissionsHolder, IDatabaseBridgeHolder {
public interface SuperiorPlayer extends IMissionsHolder, IPersistentDataHolder, IDatabaseBridgeHolder {

/*
* General Methods
Expand Down
Expand Up @@ -126,6 +126,7 @@ private void loadPlayers() {

PlayersDeserializer.deserializeMissions(playersLoader, databaseCache);
PlayersDeserializer.deserializePlayerSettings(playersLoader, databaseCache);
PlayersDeserializer.deserializePersistentDataContainer(playersLoader, databaseCache);

playersLoader.loadAllObjects("players", resultSet -> {
plugin.getPlayers().loadPlayer(databaseCache, new DatabaseResult(resultSet));
Expand Down Expand Up @@ -168,6 +169,7 @@ private void loadIslands() {
IslandsDeserializer.deserializeVisitorHomes(islandsLoader, databaseCache);
IslandsDeserializer.deserializeIslandSettings(islandsLoader, databaseCache);
IslandsDeserializer.deserializeBankTransactions(islandsLoader, databaseCache);
IslandsDeserializer.deserializePersistentDataContainer(islandsLoader, databaseCache);

islandsLoader.loadAllObjects("islands", resultSet -> {
plugin.getGrid().createIsland(databaseCache, new DatabaseResult(resultSet));
Expand Down
Expand Up @@ -122,6 +122,10 @@ public <T extends Enum<T>> Optional<T> getEnum(String key, Class<T> enumType) {

}

public Optional<byte[]> getBlob(String key) {
return getObject(key, byte[].class);
}

private <T> Optional<T> getObject(String key, Class<T> clazz) {
Object value = getObject(key);
return Optional.ofNullable(value == null || !value.getClass().isAssignableFrom(clazz) ? null : clazz.cast(value));
Expand Down
Expand Up @@ -555,6 +555,13 @@ public static void saveBankTransaction(Island island, BankTransaction bankTransa
);
}

public static void savePersistentDataContainer(Island island) {
island.getDatabaseBridge().insertObject("islands_custom_data",
new Pair<>("island", island.getUniqueId().toString()),
new Pair<>("data", island.getPersistentDataContainer().serialize())
);
}

public static void insertIsland(Island island) {
island.getDatabaseBridge().insertObject("islands",
new Pair<>("uuid", island.getUniqueId().toString()),
Expand Down Expand Up @@ -603,6 +610,7 @@ public static void deleteIsland(Island island) {
island.getDatabaseBridge().deleteObject("islands_banks", islandFilter);
island.getDatabaseBridge().deleteObject("islands_bans", islandFilter);
island.getDatabaseBridge().deleteObject("islands_block_limits", islandFilter);
island.getDatabaseBridge().deleteObject("islands_custom_data", islandFilter);
island.getDatabaseBridge().deleteObject("islands_chests", islandFilter);
island.getDatabaseBridge().deleteObject("islands_effects", islandFilter);
island.getDatabaseBridge().deleteObject("islands_entity_limits", islandFilter);
Expand Down Expand Up @@ -636,6 +644,13 @@ public static void markBlockCountsToBeSaved(Island island) {
varsForBlockCounts.add(new Object());
}

public static void markPersistentDataContainerToBeSaved(Island island) {
Set<Object> varsForPersistentData = SAVE_METHODS_TO_BE_EXECUTED.computeIfAbsent(island.getUniqueId(), u -> new EnumMap<>(FutureSave.class))
.computeIfAbsent(FutureSave.PERSISTENT_DATA, e -> new HashSet<>());
if (varsForPersistentData.isEmpty())
varsForPersistentData.add(new Object());
}

public static boolean isModified(Island island) {
return SAVE_METHODS_TO_BE_EXECUTED.containsKey(island.getUniqueId());
}
Expand All @@ -652,6 +667,9 @@ public static void executeFutureSaves(Island island) {
for (Object islandChest : futureSaveEntry.getValue())
saveIslandChest(island, (IslandChest) islandChest);
break;
case PERSISTENT_DATA:
savePersistentDataContainer(island);
break;
}
}
}
Expand All @@ -668,7 +686,8 @@ private static DatabaseFilter createFilter(String id, Island island, Pair<String
private enum FutureSave {

BLOCK_COUNTS,
ISLAND_CHESTS
ISLAND_CHESTS,
PERSISTENT_DATA

}

Expand Down

0 comments on commit 2e354d3

Please sign in to comment.