diff --git a/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java b/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java index 2ed59f8b4..7305fd10b 100644 --- a/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java +++ b/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java @@ -8,9 +8,11 @@ import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -156,7 +158,7 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { // Handle storage of maps. Check if this type is a Map if (Map.class.isAssignableFrom(propertyDescriptor.getPropertyType())) { // Note that we have no idea what type this is - List collectionTypes = Util.getCollectionParameterTypes(method); + List collectionTypes = getCollectionParameterTypes(method); // collectionTypes should be 2 long Type keyType = collectionTypes.get(0); Type valueType = collectionTypes.get(1); @@ -178,7 +180,7 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { } else if (Set.class.isAssignableFrom(propertyDescriptor.getPropertyType())) { // Loop through the collection resultset // Note that we have no idea what type this is - List collectionTypes = Util.getCollectionParameterTypes(method); + List collectionTypes = getCollectionParameterTypes(method); // collectionTypes should be only 1 long Type setType = collectionTypes.get(0); Set value = new HashSet<>(); @@ -191,7 +193,7 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { } else if (List.class.isAssignableFrom(propertyDescriptor.getPropertyType())) { // Loop through the collection resultset // Note that we have no idea what type this is - List collectionTypes = Util.getCollectionParameterTypes(method); + List collectionTypes = getCollectionParameterTypes(method); // collectionTypes should be only 1 long Type setType = collectionTypes.get(0); List value = new ArrayList<>(); @@ -215,6 +217,29 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { return instance; } + /** + * Get a list of parameter types for the collection argument in this method + * @param writeMethod - write method + * @return a list of parameter types for the collection argument in this method + */ + private List getCollectionParameterTypes(Method writeMethod) { + List result = new ArrayList<>(); + // Get the return type + // This uses a trick to extract what the arguments are of the writeMethod of the field. + // In this way, we can deduce what type needs to be written at runtime. + Type[] genericParameterTypes = writeMethod.getGenericParameterTypes(); + // There could be more than one argument, so step through them + for (Type genericParameterType : genericParameterTypes) { + // If the argument is a parameter, then do something - this should always be true if the parameter is a collection + if( genericParameterType instanceof ParameterizedType ) { + // Get the actual type arguments of the parameter + Type[] parameters = ((ParameterizedType)genericParameterType).getActualTypeArguments(); + result.addAll(Arrays.asList(parameters)); + } + } + return result; + } + /** * Inserts T into the corresponding database-table * diff --git a/src/main/java/us/tastybento/bskyblock/lists/Flags.java b/src/main/java/us/tastybento/bskyblock/lists/Flags.java index c570f3672..11ae5e99d 100644 --- a/src/main/java/us/tastybento/bskyblock/lists/Flags.java +++ b/src/main/java/us/tastybento/bskyblock/lists/Flags.java @@ -9,9 +9,9 @@ import org.bukkit.Material; import us.tastybento.bskyblock.api.flags.Flag; import us.tastybento.bskyblock.api.flags.Flag.Type; +import us.tastybento.bskyblock.api.flags.FlagBuilder; import us.tastybento.bskyblock.api.flags.clicklisteners.IslandToggleClickListener; import us.tastybento.bskyblock.api.flags.clicklisteners.WorldToggleClickListener; -import us.tastybento.bskyblock.api.flags.FlagBuilder; import us.tastybento.bskyblock.listeners.flags.BlockInteractionListener; import us.tastybento.bskyblock.listeners.flags.BreakBlocksListener; import us.tastybento.bskyblock.listeners.flags.BreedingListener; diff --git a/src/main/java/us/tastybento/bskyblock/managers/IslandsManager.java b/src/main/java/us/tastybento/bskyblock/managers/IslandsManager.java index b8996b3ec..1dc1f284b 100644 --- a/src/main/java/us/tastybento/bskyblock/managers/IslandsManager.java +++ b/src/main/java/us/tastybento/bskyblock/managers/IslandsManager.java @@ -113,6 +113,10 @@ public class IslandsManager { private BSBDatabase handler; + /** + * The last locations where an island were put. + * This is not stored persistently and resets when the server starts + */ private Map last; // Metrics data private int metrics_createdcount = 0; @@ -580,59 +584,6 @@ public class IslandsManager { this.spawn.put(spawn.getWorld(), spawn); } - /** - * Checks if there is an island or blocks at this location - * @param location - the location - * @return true if island found - */ - public boolean isIsland(Location location){ - if (location == null) { - return false; - } - location = getClosestIsland(location); - if (islandCache.getIslandAt(location) != null) { - return true; - } - - if (!plugin.getSettings().isUseOwnGenerator()) { - // Block check - if (!location.getBlock().isEmpty() && !location.getBlock().isLiquid()) { - createIsland(location); - return true; - } - // Look around - for (int x = -5; x <= 5; x++) { - for (int y = 10; y <= 255; y++) { - for (int z = -5; z <= 5; z++) { - if (!location.getWorld().getBlockAt(x + location.getBlockX(), y, z + location.getBlockZ()).isEmpty() - && !location.getWorld().getBlockAt(x + location.getBlockX(), y, z + location.getBlockZ()).isLiquid()) { - createIsland(location); - return true; - } - } - } - } - } - return false; - } - - /** - * This returns the coordinate of where an island should be on the grid. - * - * @param location - the location location to query - * @return Location of closest island - */ - public Location getClosestIsland(Location location) { - int dist = plugin.getIWM().getIslandDistance(location.getWorld()); - long x = Math.round((double) location.getBlockX() / dist) * dist + plugin.getIWM().getIslandXOffset(location.getWorld()); - long z = Math.round((double) location.getBlockZ() / dist) * dist + plugin.getIWM().getIslandZOffset(location.getWorld()); - long y = plugin.getIWM().getIslandHeight(location.getWorld()); - if (location.getBlockX() == x && location.getBlockZ() == z) { - return location; - } - return new Location(location.getWorld(), x, y, z); - } - /** * @param uniqueId - unique ID * @return true if the player is the owner of their island, i.e., owner or team leader diff --git a/src/main/java/us/tastybento/bskyblock/managers/island/NewIsland.java b/src/main/java/us/tastybento/bskyblock/managers/island/NewIsland.java index 8a396f177..32d8ff0ac 100644 --- a/src/main/java/us/tastybento/bskyblock/managers/island/NewIsland.java +++ b/src/main/java/us/tastybento/bskyblock/managers/island/NewIsland.java @@ -1,6 +1,8 @@ package us.tastybento.bskyblock.managers.island; import java.io.IOException; +import java.util.EnumMap; +import java.util.Map; import org.bukkit.Location; import org.bukkit.World; @@ -12,6 +14,7 @@ import us.tastybento.bskyblock.api.events.island.IslandEvent; import us.tastybento.bskyblock.api.events.island.IslandEvent.Reason; import us.tastybento.bskyblock.api.user.User; import us.tastybento.bskyblock.database.objects.Island; +import us.tastybento.bskyblock.util.Util; /** * Create and paste a new island @@ -19,11 +22,18 @@ import us.tastybento.bskyblock.database.objects.Island; * */ public class NewIsland { + private static final Integer MAX_UNOWNED_ISLANDS = 10; private BSkyBlock plugin; private Island island; private final User user; private final Reason reason; private final World world; + private enum Result { + ISLAND_FOUND, + BLOCK_AT_CENTER, + BLOCKS_IN_AREA, + FREE + } private NewIsland(Island oldIsland, User user, Reason reason, World world) { super(); @@ -100,6 +110,10 @@ public class NewIsland { */ public void newIsland() { Location next = getNextIsland(); + if (next == null) { + plugin.logError("Failed to make island - no unoccupied spot found"); + return; + } // Add to the grid island = plugin.getIslands().createIsland(next, user.getUniqueId()); // Save the player so that if the server is reset weird things won't happen @@ -163,7 +177,7 @@ public class NewIsland { /** * Get the location of next free island spot - * @return Location of island spot + * @return Location of island spot or null if one cannot be found */ private Location getNextIsland() { Location last = plugin.getIslands().getLast(world); @@ -171,13 +185,62 @@ public class NewIsland { last = new Location(world, plugin.getIWM().getIslandXOffset(world) + plugin.getIWM().getIslandStartX(world), plugin.getIWM().getIslandHeight(world), plugin.getIWM().getIslandZOffset(world) + plugin.getIWM().getIslandStartZ(world)); } - Location next = last.clone(); - while (plugin.getIslands().isIsland(next)) { - next = nextGridLocation(next); + // Find a free spot + Map result = new EnumMap<>(Result.class); + Result r = isIsland(last); + while (!r.equals(Result.FREE) + && (result.getOrDefault(Result.BLOCK_AT_CENTER, 0) < MAX_UNOWNED_ISLANDS + && result.getOrDefault(Result.BLOCK_AT_CENTER, 0) < MAX_UNOWNED_ISLANDS)) { + last = nextGridLocation(last); + result.merge(r, 1, (k,v) -> v++); + r = isIsland(last); } - return next; + if (!r.equals(Result.FREE)) { + // We could not find a free spot within the limit required. It's likely this world is not empty + plugin.logError("Could not find a free spot for islands! Is this world empty?"); + plugin.logError("Blocks at center locations: " + result.getOrDefault(Result.BLOCK_AT_CENTER, 0) + " max " + MAX_UNOWNED_ISLANDS); + plugin.logError("Blocks around center locations: " + result.getOrDefault(Result.BLOCKS_IN_AREA, 0) + " max " + MAX_UNOWNED_ISLANDS); + plugin.logError("Known islands: " + result.getOrDefault(Result.ISLAND_FOUND, 0) + " max unlimited."); + return null; + } + plugin.getIslands().setLast(last); + return last; } + /** + * Checks if there is an island or blocks at this location + * @param location - the location + * @return true if island found, null if blocks found, false if nothing found + */ + private Result isIsland(Location location){ + location = Util.getClosestIsland(location); + if (plugin.getIslands().getIslandAt(location).isPresent()) { + return Result.ISLAND_FOUND; + } + + if (!plugin.getSettings().isUseOwnGenerator()) { + // Block check + if (!location.getBlock().isEmpty() && !location.getBlock().isLiquid()) { + plugin.getIslands().createIsland(location); + return Result.BLOCK_AT_CENTER; + } + // Look around + for (int x = -5; x <= 5; x++) { + for (int y = 10; y < location.getWorld().getMaxHeight(); y++) { + for (int z = -5; z <= 5; z++) { + if (!location.getWorld().getBlockAt(x + location.getBlockX(), y, z + location.getBlockZ()).isEmpty() + && !location.getWorld().getBlockAt(x + location.getBlockX(), y, z + location.getBlockZ()).isLiquid()) { + plugin.getIslands().createIsland(location); + return Result.BLOCKS_IN_AREA; + } + } + } + } + } + return Result.FREE; + } + + /** * Finds the next free island spot based off the last known island Uses * island_distance setting from the config file Builds up in a grid fashion diff --git a/src/main/java/us/tastybento/bskyblock/util/Util.java b/src/main/java/us/tastybento/bskyblock/util/Util.java index 9e12b9b43..7262af753 100755 --- a/src/main/java/us/tastybento/bskyblock/util/Util.java +++ b/src/main/java/us/tastybento/bskyblock/util/Util.java @@ -1,10 +1,6 @@ package us.tastybento.bskyblock.util; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -46,12 +42,29 @@ public class Util { */ public static String getServerVersion() { if (serverVersion == null) { - String serverPackageName = plugin.getServer().getClass().getPackage().getName(); + String serverPackageName = Bukkit.getServer().getClass().getPackage().getName(); serverVersion = serverPackageName.substring(serverPackageName.lastIndexOf('.') + 1); } return serverVersion; } + /** + * This returns the coordinate of where an island should be on the grid. + * + * @param location - the location location to query + * @return Location of closest island + */ + public static Location getClosestIsland(Location location) { + int dist = plugin.getIWM().getIslandDistance(location.getWorld()); + long x = Math.round((double) location.getBlockX() / dist) * dist + plugin.getIWM().getIslandXOffset(location.getWorld()); + long z = Math.round((double) location.getBlockZ() / dist) * dist + plugin.getIWM().getIslandZOffset(location.getWorld()); + if (location.getBlockX() == x && location.getBlockZ() == z) { + return location; + } + int y = plugin.getIWM().getIslandHeight(location.getWorld()); + return new Location(location.getWorld(), x, y, z); + } + /** * Converts a serialized location to a Location. Returns null if string is * empty @@ -98,29 +111,6 @@ public class Util { return String.valueOf(Math.round(num * 100D) / 100D); } - /** - * Get a list of parameter types for the collection argument in this method - * @param writeMethod - write method - * @return a list of parameter types for the collection argument in this method - */ - public static List getCollectionParameterTypes(Method writeMethod) { - List result = new ArrayList<>(); - // Get the return type - // This uses a trick to extract what the arguments are of the writeMethod of the field. - // In this way, we can deduce what type needs to be written at runtime. - Type[] genericParameterTypes = writeMethod.getGenericParameterTypes(); - // There could be more than one argument, so step through them - for (Type genericParameterType : genericParameterTypes) { - // If the argument is a parameter, then do something - this should always be true if the parameter is a collection - if( genericParameterType instanceof ParameterizedType ) { - // Get the actual type arguments of the parameter - Type[] parameters = ((ParameterizedType)genericParameterType).getActualTypeArguments(); - result.addAll(Arrays.asList(parameters)); - } - } - return result; - } - /** * Converts a name like IRON_INGOT into Iron Ingot to improve readability * diff --git a/src/test/java/us/tastybento/bskyblock/managers/IslandsManagerTest.java b/src/test/java/us/tastybento/bskyblock/managers/IslandsManagerTest.java index 57dd5e939..5c1a2fab3 100644 --- a/src/test/java/us/tastybento/bskyblock/managers/IslandsManagerTest.java +++ b/src/test/java/us/tastybento/bskyblock/managers/IslandsManagerTest.java @@ -652,77 +652,6 @@ public class IslandsManagerTest { assertTrue(im.isAtSpawn(location)); } - /** - * Test method for {@link us.tastybento.bskyblock.managers.IslandsManager#isIsland(org.bukkit.Location)}. - * @throws Exception - */ - @Test - public void testIsIsland() throws Exception { - // Mock island cache - IslandCache ic = mock(IslandCache.class); - Island is = mock(Island.class); - - when(ic.getIslandAt(Mockito.any())).thenReturn(is); - - PowerMockito.whenNew(IslandCache.class).withAnyArguments().thenReturn(ic); - - // In world - IslandsManager im = new IslandsManager(plugin); - assertFalse(im.isIsland(null)); - im.createIsland(location); - assertTrue(im.isIsland(location)); - - // No island in cache - when(ic.getIslandAt(Mockito.any())).thenReturn(null); - - // Use own generator - when(s.isUseOwnGenerator()).thenReturn(true); - assertFalse(im.isIsland(location)); - - when(s.isUseOwnGenerator()).thenReturn(false); - - // Location - when(location.getWorld()).thenReturn(world); - when(location.getBlockX()).thenReturn(10); - when(location.getBlockZ()).thenReturn(10); - when(location.getBlock()).thenReturn(space1); - - PowerMockito.whenNew(Location.class).withAnyArguments().thenReturn(location); - - when(location.getBlock()).thenReturn(space1); - - when(world.getBlockAt(Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt())).thenReturn(ground); - - // Liquid - when(space1.isEmpty()).thenReturn(false); - when(space1.isLiquid()).thenReturn(true); - assertTrue(im.isIsland(location)); - - // Empty space - when(space1.isEmpty()).thenReturn(true); - when(space1.isLiquid()).thenReturn(false); - assertTrue(im.isIsland(location)); - - } - - /** - * Test method for {@link us.tastybento.bskyblock.managers.IslandsManager#getClosestIsland(org.bukkit.Location)}. - * @throws Exception - */ - @Test - public void testGetClosestIsland() throws Exception { - when(iwm.getIslandDistance(world)).thenReturn(100); - when(iwm.getIslandXOffset(world)).thenReturn(0); - when(iwm.getIslandZOffset(world)).thenReturn(0); - when(iwm.getIslandHeight(world)).thenReturn(120); - IslandsManager im = new IslandsManager(plugin); - when(location.getBlockX()).thenReturn(456); - when(location.getBlockZ()).thenReturn(456); - Location l = im.getClosestIsland(location); - assertEquals(500, l.getBlockX()); - assertEquals(500, l.getBlockZ()); - } - /** * Test method for {@link us.tastybento.bskyblock.managers.IslandsManager#isOwner(java.util.UUID)}. * @throws Exception diff --git a/src/test/java/us/tastybento/bskyblock/util/UtilTest.java b/src/test/java/us/tastybento/bskyblock/util/UtilTest.java new file mode 100644 index 000000000..0414371c0 --- /dev/null +++ b/src/test/java/us/tastybento/bskyblock/util/UtilTest.java @@ -0,0 +1,190 @@ +/** + * + */ +package us.tastybento.bskyblock.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.World; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import us.tastybento.bskyblock.BSkyBlock; +import us.tastybento.bskyblock.managers.IslandWorldManager; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest( { Bukkit.class }) +public class UtilTest { + + private BSkyBlock plugin; + private World world; + private IslandWorldManager iwm; + private Location location; + private Server server; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Set up plugin + plugin = mock(BSkyBlock.class); + Util.setPlugin(plugin); + // World + world = mock(World.class); + when(world.getName()).thenReturn("world_name"); + // Worlds + iwm = mock(IslandWorldManager.class); + when(plugin.getIWM()).thenReturn(iwm); + location = mock(Location.class); + when(location.getWorld()).thenReturn(world); + when(location.getX()).thenReturn(500D); + when(location.getY()).thenReturn(600D); + when(location.getZ()).thenReturn(700D); + when(location.getYaw()).thenReturn(10F); + when(location.getPitch()).thenReturn(20F); + + PowerMockito.mockStatic(Bukkit.class); + server = mock(Server.class); + when(Bukkit.getServer()).thenReturn(server); + when(server.getWorld(Mockito.anyString())).thenReturn(world); + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#getServerVersion()}. + */ + @Test + public void testGetServerVersion() { + assertEquals("bukkit",Util.getServerVersion()); + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#getClosestIsland(org.bukkit.Location)}. + */ + @Test + public void testGetClosestIsland() throws Exception { + Util.setPlugin(plugin); + when(plugin.getIWM()).thenReturn(iwm); + when(iwm.getIslandDistance(world)).thenReturn(100); + when(iwm.getIslandXOffset(world)).thenReturn(0); + when(iwm.getIslandZOffset(world)).thenReturn(0); + when(iwm.getIslandHeight(world)).thenReturn(120); + when(location.getBlockX()).thenReturn(456); + when(location.getBlockZ()).thenReturn(456); + Location l = Util.getClosestIsland(location); + assertEquals(500, l.getBlockX()); + assertEquals(500, l.getBlockZ()); + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#getLocationString(java.lang.String)}. + */ + @Test + public void testGetLocationString() { + assertNull(Util.getLocationString(null)); + assertNull(Util.getLocationString("")); + assertNull(Util.getLocationString(" ")); + Location result = Util.getLocationString("world_name:500.0:600.0:700.0:1092616192:1101004800"); + assertEquals(world, result.getWorld()); + assertTrue(result.getX() == 500D); + assertTrue(result.getY() == 600D); + assertTrue(result.getZ() == 700D); + assertTrue(result.getYaw() == 10F); + assertTrue(result.getPitch() == 20F); + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#getStringLocation(org.bukkit.Location)}. + */ + @Test + public void testGetStringLocation() { + assertEquals("", Util.getStringLocation(null)); + when(location.getWorld()).thenReturn(null); + assertEquals("", Util.getStringLocation(location)); + when(location.getWorld()).thenReturn(world); + assertEquals("world_name:500.0:600.0:700.0:1092616192:1101004800", Util.getStringLocation(location)); + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#prettifyText(java.lang.String)}. + */ + @Test + public void testPrettifyText() { + assertEquals("Hello There This Is A Test", Util.prettifyText("HELLO_THERE_THIS_IS_A_TEST")); + assertEquals("All caps test", Util.prettifyText("ALL CAPS TEST")); + assertEquals("First capital letter", Util.prettifyText("first capital letter")); + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#getOnlinePlayerList(us.tastybento.bskyblock.api.user.User)}. + */ + @Test + public void testGetOnlinePlayerList() { + //fail("Not yet implemented"); // TODO + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#tabLimit(java.util.List, java.lang.String)}. + */ + @Test + public void testTabLimit() { + //fail("Not yet implemented"); // TODO + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#getPermValue(org.bukkit.entity.Player, java.lang.String, int)}. + */ + @Test + public void testGetPermValue() { + //fail("Not yet implemented"); // TODO + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#xyz(org.bukkit.util.Vector)}. + */ + @Test + public void testXyz() { + //fail("Not yet implemented"); // TODO + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#sameWorld(org.bukkit.World, org.bukkit.World)}. + */ + @Test + public void testSameWorld() { + //fail("Not yet implemented"); // TODO + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#getWorld(org.bukkit.World)}. + */ + @Test + public void testGetWorld() { + //fail("Not yet implemented"); // TODO + } + + /** + * Test method for {@link us.tastybento.bskyblock.util.Util#blockFaceToFloat(org.bukkit.block.BlockFace)}. + */ + @Test + public void testBlockFaceToFloat() { + //fail("Not yet implemented"); // TODO + } + +}