diff --git a/src/main/java/world/bentobox/bentobox/api/metadata/MetaDataAble.java b/src/main/java/world/bentobox/bentobox/api/metadata/MetaDataAble.java new file mode 100644 index 000000000..ce63fc552 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/metadata/MetaDataAble.java @@ -0,0 +1,48 @@ +package world.bentobox.bentobox.api.metadata; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * This interface is for all BentoBox objects that have meta data + * @author tastybento + * @since 1.15.4 + */ +public interface MetaDataAble { + /** + * @return the metaData + */ + public Map getMetaData(); + + /** + * Get meta data by key + * @param key - key + * @return the value to which the specified key is mapped, or null if there is no mapping for the key + * @since 1.15.4 + */ + public MetaDataValue getMetaData(@NonNull String key); + + /** + * @param metaData the metaData to set + * @since 1.15.4 + */ + public void setMetaData(Map metaData); + + /** + * Put a key, value string pair into the object's meta data + * @param key - key + * @param value - value + * @return the previous value associated with key, or null if there was no mapping for key. + * @since 1.15.4 + */ + public MetaDataValue putMetaData(@NonNull String key, @NonNull MetaDataValue value); + + /** + * Remove meta data + * @param key - key to remove + * @return the previous value associated with key, or null if there was no mapping for key. + * @since 1.15.4 + */ + public MetaDataValue removeMetaData(@NonNull String key); +} diff --git a/src/main/java/world/bentobox/bentobox/api/metadata/MetaDataValue.java b/src/main/java/world/bentobox/bentobox/api/metadata/MetaDataValue.java new file mode 100644 index 000000000..4ae5c6d14 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/metadata/MetaDataValue.java @@ -0,0 +1,90 @@ +package world.bentobox.bentobox.api.metadata; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.Expose; + +/** + * Stores meta data value in a GSON friendly way so it can be serialized and deserialized. + * Values that are null are not stored in the database, so only the appropriate type is stored. + * @author tastybento + * @since 1.15.4 + * + */ +public class MetaDataValue { + + // Use classes so null value is supported + @Expose + private Integer intValue; + @Expose + private Float floatValue; + @Expose + private Double doubleValue; + @Expose + private Long longValue; + @Expose + private Short shortValue; + @Expose + private Byte byteValue; + @Expose + private Boolean booleanValue; + @Expose + private @NonNull String stringValue; + + /** + * Initialize this meta data value + * @param value the value assigned to this metadata value + */ + public MetaDataValue(@NonNull Object value) { + if (value instanceof Integer) { + intValue = (int)value; + } else if (value instanceof Float) { + floatValue = (float)value; + } else if (value instanceof Double) { + doubleValue = (double)value; + } else if (value instanceof Long) { + longValue = (long)value; + } else if (value instanceof Short) { + shortValue = (short)value; + } else if (value instanceof Byte) { + byteValue = (byte)value; + } else if (value instanceof Boolean) { + booleanValue = (boolean)value; + } else if (value instanceof String) { + stringValue = (String)value; + } + } + + public int asInt() { + return intValue; + } + + public float asFloat() { + return floatValue; + } + + public double asDouble() { + return doubleValue; + } + + public long asLong() { + return longValue; + } + + public short asShort() { + return shortValue; + } + + public byte asByte() { + return byteValue; + } + + public boolean asBoolean() { + return booleanValue; + } + + @NonNull + public String asString() { + return stringValue; + } +} diff --git a/src/main/java/world/bentobox/bentobox/api/user/User.java b/src/main/java/world/bentobox/bentobox/api/user/User.java index d2c1e57f8..cfe986526 100644 --- a/src/main/java/world/bentobox/bentobox/api/user/User.java +++ b/src/main/java/world/bentobox/bentobox/api/user/User.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -29,6 +30,7 @@ import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.events.OfflineMessageEvent; +import world.bentobox.bentobox.api.metadata.MetaDataValue; import world.bentobox.bentobox.util.Util; /** @@ -631,4 +633,53 @@ public class User { public void setAddon(Addon addon) { this.addon = addon; } + + /** + * Get all the meta data for this user + * @return the metaData + * @since 1.15.4 + */ + @NonNull + public Map getMetaData() { + return plugin.getPlayers().getPlayer(playerUUID).getMetaData(); + } + + /** + * Get meta data by key + * @param key - key + * @return optional value to which the specified key is mapped, or empty if there is no mapping for the key + * @since 1.15.4 + */ + public Optional getMetaData(String key) { + return Optional.ofNullable(plugin.getPlayers().getPlayer(playerUUID).getMetaData().get(key)); + } + + /** + * @param metaData the metaData to set + * @since 1.15.4 + */ + public void setMetaData(Map metaData) { + plugin.getPlayers().getPlayer(playerUUID).setMetaData(metaData); + } + + /** + * Put a key, value string pair into the user's meta data + * @param key - key + * @param value - value + * @return the previous value associated with key, or empty if there was no mapping for key. + * @since 1.15.4 + */ + public Optional putMetaData(String key, MetaDataValue value) { + return Optional.ofNullable(plugin.getPlayers().getPlayer(playerUUID).getMetaData().put(key, value)); + } + + /** + * Remove meta data + * @param key - key to remove + * @return the previous value associated with key, or empty if there was no mapping for key. + * @since 1.15.4 + */ + public Optional removeMetaData(String key) { + return Optional.ofNullable(plugin.getPlayers().getPlayer(playerUUID).getMetaData().remove(key)); + } } diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index c19b09be7..4e638b8cb 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -34,6 +34,8 @@ import world.bentobox.bentobox.api.events.island.IslandEvent; import world.bentobox.bentobox.api.flags.Flag; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.logs.LogEntry; +import world.bentobox.bentobox.api.metadata.MetaDataAble; +import world.bentobox.bentobox.api.metadata.MetaDataValue; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.adapters.Adapter; import world.bentobox.bentobox.database.objects.adapters.FlagSerializer; @@ -54,7 +56,7 @@ import world.bentobox.bentobox.util.Util; * @author Poslovitch */ @Table(name = "Islands") -public class Island implements DataObject { +public class Island implements DataObject, MetaDataAble { // True if this island is deleted and pending deletion from the database @Expose @@ -171,6 +173,13 @@ public class Island implements DataObject { @Nullable private Boolean reserved = null; + /** + * A place to store meta data for this island. + * @since 1.15.4 + */ + @Expose + private Map metaData; + /* * *************************** Constructors ****************************** */ @@ -956,14 +965,14 @@ public class Island implements DataObject { // Fixes #getLastPlayed() returning 0 when it is the owner's first connection. long lastPlayed = (Bukkit.getServer().getOfflinePlayer(owner).getLastPlayed() != 0) ? Bukkit.getServer().getOfflinePlayer(owner).getLastPlayed() : Bukkit.getServer().getOfflinePlayer(owner).getFirstPlayed(); - user.sendMessage("commands.admin.info.last-login","[date]", new Date(lastPlayed).toString()); + user.sendMessage("commands.admin.info.last-login","[date]", new Date(lastPlayed).toString()); - user.sendMessage("commands.admin.info.deaths", "[number]", String.valueOf(plugin.getPlayers().getDeaths(world, owner))); - String resets = String.valueOf(plugin.getPlayers().getResets(world, owner)); - String total = plugin.getIWM().getResetLimit(world) < 0 ? "Unlimited" : String.valueOf(plugin.getIWM().getResetLimit(world)); - user.sendMessage("commands.admin.info.resets-left", "[number]", resets, "[total]", total); - // Show team members - showMembers(user); + user.sendMessage("commands.admin.info.deaths", "[number]", String.valueOf(plugin.getPlayers().getDeaths(world, owner))); + String resets = String.valueOf(plugin.getPlayers().getResets(world, owner)); + String total = plugin.getIWM().getResetLimit(world) < 0 ? "Unlimited" : String.valueOf(plugin.getIWM().getResetLimit(world)); + user.sendMessage("commands.admin.info.resets-left", "[number]", resets, "[total]", total); + // Show team members + showMembers(user); } Vector location = center.toVector(); user.sendMessage("commands.admin.info.island-location", "[xyz]", Util.xyz(location)); @@ -1251,4 +1260,55 @@ public class Island implements DataObject { + ", levelHandicap=" + levelHandicap + ", spawnPoint=" + spawnPoint + ", doNotLoad=" + doNotLoad + "]"; } + /** + * @return the metaData + * @since 1.15.4 + */ + @Override + public Map getMetaData() { + return metaData; + } + + /** + * Get meta data by key + * @param key - key + * @return the value to which the specified key is mapped, or null if there is no mapping for the key + * @since 1.15.4 + */ + @Override + public MetaDataValue getMetaData(String key) { + return this.metaData.get(key); + } + + /** + * @param metaData the metaData to set + * @since 1.15.4 + */ + @Override + public void setMetaData(Map metaData) { + this.metaData = metaData; + } + + /** + * Put a key, value string pair into the island's meta data + * @param key - key + * @param value - value + * @return the previous value associated with key, or null if there was no mapping for key. + * @since 1.15.4 + */ + @Override + public MetaDataValue putMetaData(String key, MetaDataValue value) { + return this.metaData.put(key, value); + } + + /** + * Remove meta data + * @param key - key to remove + * @return the previous value associated with key, or null if there was no mapping for key. + * @since 1.15.4 + */ + @Override + public MetaDataValue removeMetaData(String key) { + return this.metaData.remove(key); + } } diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Players.java b/src/main/java/world/bentobox/bentobox/database/objects/Players.java index c9dbf04c4..dc1ce9322 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Players.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Players.java @@ -11,12 +11,15 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import com.google.gson.annotations.Expose; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.flags.Flag; +import world.bentobox.bentobox.api.metadata.MetaDataAble; +import world.bentobox.bentobox.api.metadata.MetaDataValue; import world.bentobox.bentobox.util.Util; /** @@ -25,7 +28,7 @@ import world.bentobox.bentobox.util.Util; * @author tastybento */ @Table(name = "Players") -public class Players implements DataObject { +public class Players implements DataObject, MetaDataAble { @Expose private Map homeLocations = new HashMap<>(); @Expose @@ -53,6 +56,13 @@ public class Players implements DataObject { @Expose private Flag.Mode flagsDisplayMode = Flag.Mode.BASIC; + /** + * A place to store meta data for this player. + * @since 1.15.4 + */ + @Expose + private Map metaData; + /** * This is required for database storage */ @@ -338,4 +348,59 @@ public class Players implements DataObject { public void setFlagsDisplayMode(Flag.Mode flagsDisplayMode) { this.flagsDisplayMode = flagsDisplayMode; } + + /** + * @return the metaData + */ + @Override + public Map getMetaData() { + if (metaData == null) { + metaData = new HashMap<>(); + } + return metaData; + } + + /** + * Get meta data by key + * @param key - key + * @return the value to which the specified key is mapped, or null if there is no mapping for the key + * @since 1.15.4 + */ + @Override + public MetaDataValue getMetaData(@NonNull String key) { + return getMetaData().get(key); + } + + /** + * @param metaData the metaData to set + * @since 1.15.4 + */ + @Override + public void setMetaData(Map metaData) { + this.metaData = metaData; + } + + /** + * Put a key, value string pair into the player's meta data + * @param key - key + * @param value - value + * @return the previous value associated with key, or null if there was no mapping for key. + * @since 1.15.4 + */ + @Override + public MetaDataValue putMetaData(@NonNull String key, @NonNull MetaDataValue value) { + return getMetaData().put(key, value); + } + + /** + * Remove meta data + * @param key - key to remove + * @return the previous value associated with key, or null if there was no mapping for key. + * @since 1.15.4 + */ + @Override + public MetaDataValue removeMetaData(@NonNull String key) { + return getMetaData().remove(key); + } + } diff --git a/src/test/java/world/bentobox/bentobox/api/metadata/MetaDataValueTest.java b/src/test/java/world/bentobox/bentobox/api/metadata/MetaDataValueTest.java new file mode 100644 index 000000000..9f5f1aa5f --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/api/metadata/MetaDataValueTest.java @@ -0,0 +1,92 @@ +package world.bentobox.bentobox.api.metadata; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +public class MetaDataValueTest { + + /** + * Test method for {@link world.bentobox.bentobox.api.metadata.MetaDataValue#asInt()}. + */ + @Test + public void testAsInt() { + MetaDataValue mdv = new MetaDataValue(123); + assertEquals(123, mdv.asInt()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.metadata.MetaDataValue#asFloat()}. + */ + @Test + public void testAsFloat() { + MetaDataValue mdv = new MetaDataValue(123.34F); + assertEquals(123.34F, mdv.asFloat(), 0F); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.metadata.MetaDataValue#asDouble()}. + */ + @Test + public void testAsDouble() { + MetaDataValue mdv = new MetaDataValue(123.3444D); + assertEquals(123.3444D, mdv.asDouble(), 0D); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.metadata.MetaDataValue#asLong()}. + */ + @Test + public void testAsLong() { + MetaDataValue mdv = new MetaDataValue(123456L); + assertEquals(123456L, mdv.asLong()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.metadata.MetaDataValue#asShort()}. + */ + @Test + public void testAsShort() { + MetaDataValue mdv = new MetaDataValue((short)12); + assertEquals((short)12, mdv.asShort()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.metadata.MetaDataValue#asByte()}. + */ + @Test + public void testAsByte() { + MetaDataValue mdv = new MetaDataValue((byte)12); + assertEquals((byte)12, mdv.asByte()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.metadata.MetaDataValue#asBoolean()}. + */ + @Test + public void testAsBoolean() { + MetaDataValue mdv = new MetaDataValue(false); + assertFalse(mdv.asBoolean()); + mdv = new MetaDataValue(true); + assertTrue(mdv.asBoolean()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.metadata.MetaDataValue#asString()}. + */ + @Test + public void testAsString() { + MetaDataValue mdv = new MetaDataValue("a string"); + assertEquals("a string", mdv.asString()); + } + +} diff --git a/src/test/java/world/bentobox/bentobox/api/user/UserTest.java b/src/test/java/world/bentobox/bentobox/api/user/UserTest.java index f3e8fe1ee..7ff1e1854 100644 --- a/src/test/java/world/bentobox/bentobox/api/user/UserTest.java +++ b/src/test/java/world/bentobox/bentobox/api/user/UserTest.java @@ -15,6 +15,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Optional; @@ -33,6 +34,7 @@ import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.PlayerInventory; import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.plugin.PluginManager; +import org.eclipse.jdt.annotation.Nullable; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -49,6 +51,8 @@ import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.addons.AddonDescription; import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.metadata.MetaDataValue; +import world.bentobox.bentobox.database.objects.Players; import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.LocalesManager; import world.bentobox.bentobox.managers.PlaceholdersManager; @@ -80,6 +84,7 @@ public class UserTest { private Server server; @Mock private PlayersManager pm; + private @Nullable Players players; @Before public void setUp() throws Exception { @@ -120,6 +125,8 @@ public class UserTest { when(placeholdersManager.replacePlaceholders(any(), any())).thenAnswer((Answer) invocation -> invocation.getArgument(1, String.class)); when(plugin.getPlayers()).thenReturn(pm); + players = new Players(); + when(pm.getPlayer(any())).thenReturn(players); } @After @@ -592,4 +599,30 @@ public class UserTest { User u = User.getInstance(player); assertEquals(3, u.getPermissionValue("bskyblock.max", 22)); } + + @Test + public void testMetaData() { + User u = User.getInstance(player); + assertTrue(u.getMetaData().isEmpty()); + // Store a string in a new key + assertFalse(u.putMetaData("string", new MetaDataValue("a string")).isPresent()); + // Store an int in a new key + assertFalse(u.putMetaData("int", new MetaDataValue(1234)).isPresent()); + // Overwrite the string with the same key + assertEquals("a string", u.putMetaData("string", new MetaDataValue("a new string")).get().asString()); + // Get the new string with the same key + assertEquals("a new string", u.getMetaData("string").get().asString()); + // Try to get a non-existent key + assertFalse(u.getMetaData("boogie").isPresent()); + // Remove existing key + assertEquals(1234, u.removeMetaData("int").get().asInt()); + assertFalse(u.getMetaData("int").isPresent()); + // Try to remove non-existent key + assertFalse(u.removeMetaData("ggogg").isPresent()); + // Set the meta data as blank + assertFalse(u.getMetaData().isEmpty()); + u.setMetaData(new HashMap<>()); + assertTrue(u.getMetaData().isEmpty()); + } + }