Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.mvplugins.multiverse.core.utils;

import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

import java.util.Collection;

Check warning on line 7 in src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Wrong order for 'java.util.Collection' import. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java:7:1: warning: Wrong order for 'java.util.Collection' import. (com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck)
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
* A map with case-insensitive String keys. All keys are stored in lower-case form.
*
* @param <T> the type of mapped values
*
* @since 5.5
*/
@ApiStatus.AvailableSince("5.5")
public class CaseInsensitiveStringMap<T> implements Map<String, T> {

private final Map<String, T> map;

public CaseInsensitiveStringMap() {

Check warning on line 25 in src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Missing a Javadoc comment. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java:25:5: warning: Missing a Javadoc comment. (com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck)
map = new HashMap<>();
}

@Override

Check warning on line 29 in src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'size' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'size' static/final/abstract/empty, or adding allowed annotation for the method. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java:29:5: info: Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'size' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'size' static/final/abstract/empty, or adding allowed annotation for the method. (com.puppycrawl.tools.checkstyle.checks.design.DesignForExtensionCheck)
public int size() {
return map.size();
}

@Override

Check warning on line 34 in src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'isEmpty' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'isEmpty' static/final/abstract/empty, or adding allowed annotation for the method. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java:34:5: info: Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'isEmpty' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'isEmpty' static/final/abstract/empty, or adding allowed annotation for the method. (com.puppycrawl.tools.checkstyle.checks.design.DesignForExtensionCheck)
public boolean isEmpty() {
return map.isEmpty();
}

@Override

Check warning on line 39 in src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'containsKey' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'containsKey' static/final/abstract/empty, or adding allowed annotation for the method. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java:39:5: info: Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'containsKey' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'containsKey' static/final/abstract/empty, or adding allowed annotation for the method. (com.puppycrawl.tools.checkstyle.checks.design.DesignForExtensionCheck)
public boolean containsKey(Object key) {
return map.containsKey(normalizeKey(key));
}

@Override

Check warning on line 44 in src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'containsValue' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'containsValue' static/final/abstract/empty, or adding allowed annotation for the method. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java:44:5: info: Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'containsValue' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'containsValue' static/final/abstract/empty, or adding allowed annotation for the method. (com.puppycrawl.tools.checkstyle.checks.design.DesignForExtensionCheck)
public boolean containsValue(Object value) {
return map.containsValue(value);
}

@Override

Check warning on line 49 in src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'get' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'get' static/final/abstract/empty, or adding allowed annotation for the method. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java:49:5: info: Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'get' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'get' static/final/abstract/empty, or adding allowed annotation for the method. (com.puppycrawl.tools.checkstyle.checks.design.DesignForExtensionCheck)
public T get(Object key) {
return map.get(normalizeKey(key));
}

@Override

Check warning on line 54 in src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'put' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'put' static/final/abstract/empty, or adding allowed annotation for the method. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java:54:5: info: Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'put' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'put' static/final/abstract/empty, or adding allowed annotation for the method. (com.puppycrawl.tools.checkstyle.checks.design.DesignForExtensionCheck)
public @Nullable T put(String key, T value) {
return map.put(normalizeKey(key), value);
}

@Override

Check warning on line 59 in src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'remove' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'remove' static/final/abstract/empty, or adding allowed annotation for the method. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java:59:5: info: Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'remove' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'remove' static/final/abstract/empty, or adding allowed annotation for the method. (com.puppycrawl.tools.checkstyle.checks.design.DesignForExtensionCheck)
public T remove(Object key) {
return map.remove(normalizeKey(key));
}

@Override

Check warning on line 64 in src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'putAll' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'putAll' static/final/abstract/empty, or adding allowed annotation for the method. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/CaseInsensitiveStringMap.java:64:5: info: Class 'CaseInsensitiveStringMap' looks like designed for extension (can be subclassed), but the method 'putAll' does not have javadoc that explains how to do that safely. If class is not designed for extension consider making the class 'CaseInsensitiveStringMap' final or making the method 'putAll' static/final/abstract/empty, or adding allowed annotation for the method. (com.puppycrawl.tools.checkstyle.checks.design.DesignForExtensionCheck)
public void putAll(@NonNull Map<? extends String, ? extends T> m) {
m.forEach((key, value) -> map.put(normalizeKey(key), value));
}

@Override
public void clear() {
map.clear();
}

@Override
public @NonNull Set<String> keySet() {
return map.keySet();
}

@Override
public @NonNull Collection<T> values() {
return map.values();
}

@Override
public @NonNull Set<Entry<String, T>> entrySet() {
return map.entrySet();
}

private String normalizeKey(Object key) {
return String.valueOf(key).toLowerCase(Locale.ROOT);
}
}
23 changes: 12 additions & 11 deletions src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.mvplugins.multiverse.core.permissions.CorePermissions;
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
import org.mvplugins.multiverse.core.utils.CaseInsensitiveStringMap;
import org.mvplugins.multiverse.core.utils.ReflectHelper;
import org.mvplugins.multiverse.core.utils.ServerProperties;
import org.mvplugins.multiverse.core.utils.result.Attempt;
Expand Down Expand Up @@ -91,8 +92,8 @@ public final class WorldManager {
private static final DimensionFormat DEFAULT_NETHER_FORMAT = new DimensionFormat("%overworld%_nether");
private static final DimensionFormat DEFAULT_END_FORMAT = new DimensionFormat("%overworld%_the_end");

private final Map<String, MultiverseWorld> worldsMap;
private final Map<String, LoadedMultiverseWorld> loadedWorldsMap;
private final CaseInsensitiveStringMap<MultiverseWorld> worldsMap;
private final CaseInsensitiveStringMap<LoadedMultiverseWorld> loadedWorldsMap;
private final List<String> unloadTracker;
private final List<String> loadTracker;
private final WorldsConfigManager worldsConfigManager;
Expand Down Expand Up @@ -135,8 +136,8 @@ public final class WorldManager {
this.config = config;
this.entityPurger = entityPurger;

this.worldsMap = new HashMap<>();
this.loadedWorldsMap = new HashMap<>();
this.worldsMap = new CaseInsensitiveStringMap<>();
this.loadedWorldsMap = new CaseInsensitiveStringMap<>();
this.unloadTracker = new ArrayList<>();
this.loadTracker = new ArrayList<>();
}
Expand Down Expand Up @@ -345,7 +346,7 @@ private Attempt<LoadedMultiverseWorld, ImportFailureReason> doImportBukkitWorld(

private MultiverseWorld newMultiverseWorld(String worldName, WorldConfig worldConfig) {
MultiverseWorld mvWorld = new MultiverseWorld(worldName, worldConfig, config);
worldsMap.put(mvWorld.getName().toLowerCase(Locale.ENGLISH), mvWorld);
worldsMap.put(mvWorld.getName(), mvWorld);
corePermissions.addWorldPermissions(mvWorld);
return mvWorld;
}
Expand Down Expand Up @@ -382,7 +383,7 @@ private LoadedMultiverseWorld newLoadedMultiverseWorld(
locationManipulation,
entityPurger
);
loadedWorldsMap.put(loadedWorld.getName().toLowerCase(Locale.ENGLISH), loadedWorld);
loadedWorldsMap.put(loadedWorld.getName(), loadedWorld);
saveWorldsConfig();
pluginManager.callEvent(new MVWorldLoadedEvent(loadedWorld));
return loadedWorld;
Expand Down Expand Up @@ -501,7 +502,7 @@ private Attempt<LoadedMultiverseWorld, LoadFailureReason> newLoadedMultiverseWor
locationManipulation,
entityPurger
);
loadedWorldsMap.put(loadedWorld.getName().toLowerCase(Locale.ENGLISH), loadedWorld);
loadedWorldsMap.put(loadedWorld.getName(), loadedWorld);
saveWorldsConfig();
pluginManager.callEvent(new MVWorldLoadedEvent(loadedWorld));
return Attempt.success(loadedWorld);
Expand Down Expand Up @@ -1010,7 +1011,7 @@ public Collection<MultiverseWorld> getWorlds() {
* @return True if the world is a world is known to multiverse, but may or may not be loaded.
*/
public boolean isWorld(@Nullable String worldName) {
return worldName != null && worldsMap.containsKey(worldName.toLowerCase(Locale.ENGLISH));
return worldName != null && worldsMap.containsKey(worldName);
}

/**
Expand All @@ -1022,7 +1023,7 @@ public boolean isWorld(@Nullable String worldName) {
public Option<MultiverseWorld> getUnloadedWorld(@Nullable String worldName) {
return isLoadedWorld(worldName)
? Option.none()
: Option.of(worldName).flatMap(name -> Option.of(worldsMap.get(name.toLowerCase(Locale.ENGLISH))));
: Option.of(worldName).flatMap(name -> Option.of(worldsMap.get(name)));
}

/**
Expand Down Expand Up @@ -1095,7 +1096,7 @@ public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable MultiverseWorld wo
*/
public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable String worldName) {
return Option.of(worldName)
.flatMap(name -> Option.of(loadedWorldsMap.get(name.toLowerCase(Locale.ENGLISH))));
.flatMap(name -> Option.of(loadedWorldsMap.get(name)));
}

/**
Expand Down Expand Up @@ -1156,7 +1157,7 @@ public boolean isLoadedWorld(@Nullable MultiverseWorld world) {
* @return True if the world is a multiverse world that is loaded.
*/
public boolean isLoadedWorld(@Nullable String worldName) {
return worldName != null && loadedWorldsMap.containsKey(worldName.toLowerCase(Locale.ENGLISH));
return worldName != null && loadedWorldsMap.containsKey(worldName);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ class WorldManagerTest : TestWithMockBukkit() {
world = worldManager.getLoadedWorld("world").get()
assertNotNull(world)

assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world2")).isSuccess)
world2 = worldManager.getLoadedWorld("world2").get()
assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("World2")).isSuccess)
world2 = worldManager.getLoadedWorld("World2").get()
assertNotNull(world2)
}

Expand Down Expand Up @@ -122,6 +122,11 @@ class WorldManagerTest : TestWithMockBukkit() {
assertFalse(worldManager.getLoadedWorld("world").isDefined)
assertFalse(worldManager.getUnloadedWorld("world").isDefined)

assertTrue(worldManager.removeWorld(RemoveWorldOptions.world(world2)).isSuccess)
assertFalse(worldManager.getWorld("World2").isDefined)
assertFalse(worldManager.getLoadedWorld("World2").isDefined)
assertFalse(worldManager.getUnloadedWorld("World2").isDefined)

assertThat(server.pluginManager, hasFiredEventInstance(MVWorldUnloadedEvent::class.java))
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldRemovedEvent::class.java))
}
Expand All @@ -130,9 +135,18 @@ class WorldManagerTest : TestWithMockBukkit() {
fun `Delete world`() {
assertTrue(File(Bukkit.getWorldContainer(), "world").isDirectory)
assertTrue(worldManager.deleteWorld(DeleteWorldOptions.world(world)).isSuccess)
assertFalse(worldManager.getWorld("world").isDefined)
assertFalse(worldManager.getLoadedWorld("world").isDefined)
assertFalse(worldManager.getUnloadedWorld("world").isDefined)
assertFalse(File(Bukkit.getWorldContainer(), "world").isDirectory)

assertTrue(File(Bukkit.getWorldContainer(), "World2").isDirectory)
assertTrue(worldManager.deleteWorld(DeleteWorldOptions.world(world2)).isSuccess)
assertFalse(worldManager.getWorld("World2").isDefined)
assertFalse(worldManager.getLoadedWorld("World2").isDefined)
assertFalse(worldManager.getUnloadedWorld("World2").isDefined)
assertFalse(File(Bukkit.getWorldContainer(), "World2").isDirectory)

assertThat(server.pluginManager, hasFiredEventInstance(MVWorldDeleteEvent::class.java))
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldUnloadedEvent::class.java))
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldRemovedEvent::class.java))
Expand All @@ -143,21 +157,21 @@ class WorldManagerTest : TestWithMockBukkit() {
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2).saveBukkitWorld(true)).isSuccess)
assertFalse(world2.isLoaded)
assertFalse(world2.bukkitWorld.isDefined)
assertFalse(worldManager.getLoadedWorld("world2").isDefined)
assertTrue(worldManager.getWorld("world2").isDefined)
assertTrue(worldManager.getUnloadedWorld("world2").isDefined)
assertFalse(worldManager.getLoadedWorld("World2").isDefined)
assertTrue(worldManager.getWorld("World2").isDefined)
assertTrue(worldManager.getUnloadedWorld("World2").isDefined)

assertTrue(worldManager.loadWorld(LoadWorldOptions.world(world2)).isSuccess)
assertTrue(world2.isLoaded)
assertTrue(worldManager.getLoadedWorld("world2").flatMap{ w -> w.bukkitWorld }.isDefined)
assertTrue(worldManager.getLoadedWorld("world2").isDefined)
assertFalse(worldManager.getUnloadedWorld("world2").isDefined)
assertTrue(worldManager.getLoadedWorld("World2").flatMap{ w -> w.bukkitWorld }.isDefined)
assertTrue(worldManager.getLoadedWorld("World2").isDefined)
assertFalse(worldManager.getUnloadedWorld("World2").isDefined)
}

@Test
fun `Load world failed - invalid world folder`() {
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2)).isSuccess)
File(Bukkit.getWorldContainer(), "world2/").deleteRecursively()
File(Bukkit.getWorldContainer(), "World2/").deleteRecursively()
assertEquals(
LoadFailureReason.WORLD_FOLDER_INVALID,
worldManager.loadWorld(LoadWorldOptions.world(world2)).failureReason
Expand Down Expand Up @@ -190,7 +204,7 @@ class WorldManagerTest : TestWithMockBukkit() {
.seed(4321L)
).isSuccess)

val getWorld = worldManager.getLoadedWorld("world2")
val getWorld = worldManager.getLoadedWorld("World2")
assertTrue(getWorld.isDefined)
val world = getWorld.get()
assertNotNull(world)
Expand Down Expand Up @@ -230,7 +244,7 @@ class WorldManagerTest : TestWithMockBukkit() {
fun `Clone world failed - target world exists and loaded`() {
assertEquals(
CloneFailureReason.WORLD_EXIST_LOADED,
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "world2")).failureReason
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "World2")).failureReason
)
}

Expand All @@ -239,7 +253,7 @@ class WorldManagerTest : TestWithMockBukkit() {
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2)).isSuccess)
assertEquals(
CloneFailureReason.WORLD_EXIST_UNLOADED,
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "world2")).failureReason
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "World2")).failureReason
)
}

Expand Down
Loading