Skip to content

Commit aac0111

Browse files
authored
Merge pull request #3408 from Multiverse/fix/case-insensitive-worldname
Fix case-insensitive world name handling
2 parents 1434ca1 + f66e673 commit aac0111

File tree

3 files changed

+130
-23
lines changed

3 files changed

+130
-23
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package org.mvplugins.multiverse.core.utils;
2+
3+
import org.jetbrains.annotations.ApiStatus;
4+
import org.jspecify.annotations.NonNull;
5+
import org.jspecify.annotations.Nullable;
6+
7+
import java.util.Collection;
8+
import java.util.HashMap;
9+
import java.util.Locale;
10+
import java.util.Map;
11+
import java.util.Set;
12+
13+
/**
14+
* A map with case-insensitive String keys. All keys are stored in lower-case form.
15+
*
16+
* @param <T> the type of mapped values
17+
*
18+
* @since 5.5
19+
*/
20+
@ApiStatus.AvailableSince("5.5")
21+
public class CaseInsensitiveStringMap<T> implements Map<String, T> {
22+
23+
private final Map<String, T> map;
24+
25+
public CaseInsensitiveStringMap() {
26+
map = new HashMap<>();
27+
}
28+
29+
@Override
30+
public int size() {
31+
return map.size();
32+
}
33+
34+
@Override
35+
public boolean isEmpty() {
36+
return map.isEmpty();
37+
}
38+
39+
@Override
40+
public boolean containsKey(Object key) {
41+
return map.containsKey(normalizeKey(key));
42+
}
43+
44+
@Override
45+
public boolean containsValue(Object value) {
46+
return map.containsValue(value);
47+
}
48+
49+
@Override
50+
public T get(Object key) {
51+
return map.get(normalizeKey(key));
52+
}
53+
54+
@Override
55+
public @Nullable T put(String key, T value) {
56+
return map.put(normalizeKey(key), value);
57+
}
58+
59+
@Override
60+
public T remove(Object key) {
61+
return map.remove(normalizeKey(key));
62+
}
63+
64+
@Override
65+
public void putAll(@NonNull Map<? extends String, ? extends T> m) {
66+
m.forEach((key, value) -> map.put(normalizeKey(key), value));
67+
}
68+
69+
@Override
70+
public void clear() {
71+
map.clear();
72+
}
73+
74+
@Override
75+
public @NonNull Set<String> keySet() {
76+
return map.keySet();
77+
}
78+
79+
@Override
80+
public @NonNull Collection<T> values() {
81+
return map.values();
82+
}
83+
84+
@Override
85+
public @NonNull Set<Entry<String, T>> entrySet() {
86+
return map.entrySet();
87+
}
88+
89+
private String normalizeKey(Object key) {
90+
return String.valueOf(key).toLowerCase(Locale.ROOT);
91+
}
92+
}

src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.mvplugins.multiverse.core.permissions.CorePermissions;
4949
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
5050
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
51+
import org.mvplugins.multiverse.core.utils.CaseInsensitiveStringMap;
5152
import org.mvplugins.multiverse.core.utils.ReflectHelper;
5253
import org.mvplugins.multiverse.core.utils.ServerProperties;
5354
import org.mvplugins.multiverse.core.utils.result.Attempt;
@@ -91,8 +92,8 @@ public final class WorldManager {
9192
private static final DimensionFormat DEFAULT_NETHER_FORMAT = new DimensionFormat("%overworld%_nether");
9293
private static final DimensionFormat DEFAULT_END_FORMAT = new DimensionFormat("%overworld%_the_end");
9394

94-
private final Map<String, MultiverseWorld> worldsMap;
95-
private final Map<String, LoadedMultiverseWorld> loadedWorldsMap;
95+
private final CaseInsensitiveStringMap<MultiverseWorld> worldsMap;
96+
private final CaseInsensitiveStringMap<LoadedMultiverseWorld> loadedWorldsMap;
9697
private final List<String> unloadTracker;
9798
private final List<String> loadTracker;
9899
private final WorldsConfigManager worldsConfigManager;
@@ -135,8 +136,8 @@ public final class WorldManager {
135136
this.config = config;
136137
this.entityPurger = entityPurger;
137138

138-
this.worldsMap = new HashMap<>();
139-
this.loadedWorldsMap = new HashMap<>();
139+
this.worldsMap = new CaseInsensitiveStringMap<>();
140+
this.loadedWorldsMap = new CaseInsensitiveStringMap<>();
140141
this.unloadTracker = new ArrayList<>();
141142
this.loadTracker = new ArrayList<>();
142143
}
@@ -345,7 +346,7 @@ private Attempt<LoadedMultiverseWorld, ImportFailureReason> doImportBukkitWorld(
345346

346347
private MultiverseWorld newMultiverseWorld(String worldName, WorldConfig worldConfig) {
347348
MultiverseWorld mvWorld = new MultiverseWorld(worldName, worldConfig, config);
348-
worldsMap.put(mvWorld.getName().toLowerCase(Locale.ENGLISH), mvWorld);
349+
worldsMap.put(mvWorld.getName(), mvWorld);
349350
corePermissions.addWorldPermissions(mvWorld);
350351
return mvWorld;
351352
}
@@ -382,7 +383,7 @@ private LoadedMultiverseWorld newLoadedMultiverseWorld(
382383
locationManipulation,
383384
entityPurger
384385
);
385-
loadedWorldsMap.put(loadedWorld.getName().toLowerCase(Locale.ENGLISH), loadedWorld);
386+
loadedWorldsMap.put(loadedWorld.getName(), loadedWorld);
386387
saveWorldsConfig();
387388
pluginManager.callEvent(new MVWorldLoadedEvent(loadedWorld));
388389
return loadedWorld;
@@ -501,7 +502,7 @@ private Attempt<LoadedMultiverseWorld, LoadFailureReason> newLoadedMultiverseWor
501502
locationManipulation,
502503
entityPurger
503504
);
504-
loadedWorldsMap.put(loadedWorld.getName().toLowerCase(Locale.ENGLISH), loadedWorld);
505+
loadedWorldsMap.put(loadedWorld.getName(), loadedWorld);
505506
saveWorldsConfig();
506507
pluginManager.callEvent(new MVWorldLoadedEvent(loadedWorld));
507508
return Attempt.success(loadedWorld);
@@ -1010,7 +1011,7 @@ public Collection<MultiverseWorld> getWorlds() {
10101011
* @return True if the world is a world is known to multiverse, but may or may not be loaded.
10111012
*/
10121013
public boolean isWorld(@Nullable String worldName) {
1013-
return worldName != null && worldsMap.containsKey(worldName.toLowerCase(Locale.ENGLISH));
1014+
return worldName != null && worldsMap.containsKey(worldName);
10141015
}
10151016

10161017
/**
@@ -1022,7 +1023,7 @@ public boolean isWorld(@Nullable String worldName) {
10221023
public Option<MultiverseWorld> getUnloadedWorld(@Nullable String worldName) {
10231024
return isLoadedWorld(worldName)
10241025
? Option.none()
1025-
: Option.of(worldName).flatMap(name -> Option.of(worldsMap.get(name.toLowerCase(Locale.ENGLISH))));
1026+
: Option.of(worldName).flatMap(name -> Option.of(worldsMap.get(name)));
10261027
}
10271028

10281029
/**
@@ -1095,7 +1096,7 @@ public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable MultiverseWorld wo
10951096
*/
10961097
public Option<LoadedMultiverseWorld> getLoadedWorld(@Nullable String worldName) {
10971098
return Option.of(worldName)
1098-
.flatMap(name -> Option.of(loadedWorldsMap.get(name.toLowerCase(Locale.ENGLISH))));
1099+
.flatMap(name -> Option.of(loadedWorldsMap.get(name)));
10991100
}
11001101

11011102
/**
@@ -1156,7 +1157,7 @@ public boolean isLoadedWorld(@Nullable MultiverseWorld world) {
11561157
* @return True if the world is a multiverse world that is loaded.
11571158
*/
11581159
public boolean isLoadedWorld(@Nullable String worldName) {
1159-
return worldName != null && loadedWorldsMap.containsKey(worldName.toLowerCase(Locale.ENGLISH));
1160+
return worldName != null && loadedWorldsMap.containsKey(worldName);
11601161
}
11611162

11621163
/**

src/test/java/org/mvplugins/multiverse/core/world/WorldManagerTest.kt

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ class WorldManagerTest : TestWithMockBukkit() {
3636
world = worldManager.getLoadedWorld("world").get()
3737
assertNotNull(world)
3838

39-
assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world2")).isSuccess)
40-
world2 = worldManager.getLoadedWorld("world2").get()
39+
assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("World2")).isSuccess)
40+
world2 = worldManager.getLoadedWorld("World2").get()
4141
assertNotNull(world2)
4242
}
4343

@@ -122,6 +122,11 @@ class WorldManagerTest : TestWithMockBukkit() {
122122
assertFalse(worldManager.getLoadedWorld("world").isDefined)
123123
assertFalse(worldManager.getUnloadedWorld("world").isDefined)
124124

125+
assertTrue(worldManager.removeWorld(RemoveWorldOptions.world(world2)).isSuccess)
126+
assertFalse(worldManager.getWorld("World2").isDefined)
127+
assertFalse(worldManager.getLoadedWorld("World2").isDefined)
128+
assertFalse(worldManager.getUnloadedWorld("World2").isDefined)
129+
125130
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldUnloadedEvent::class.java))
126131
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldRemovedEvent::class.java))
127132
}
@@ -130,9 +135,18 @@ class WorldManagerTest : TestWithMockBukkit() {
130135
fun `Delete world`() {
131136
assertTrue(File(Bukkit.getWorldContainer(), "world").isDirectory)
132137
assertTrue(worldManager.deleteWorld(DeleteWorldOptions.world(world)).isSuccess)
138+
assertFalse(worldManager.getWorld("world").isDefined)
133139
assertFalse(worldManager.getLoadedWorld("world").isDefined)
140+
assertFalse(worldManager.getUnloadedWorld("world").isDefined)
134141
assertFalse(File(Bukkit.getWorldContainer(), "world").isDirectory)
135142

143+
assertTrue(File(Bukkit.getWorldContainer(), "World2").isDirectory)
144+
assertTrue(worldManager.deleteWorld(DeleteWorldOptions.world(world2)).isSuccess)
145+
assertFalse(worldManager.getWorld("World2").isDefined)
146+
assertFalse(worldManager.getLoadedWorld("World2").isDefined)
147+
assertFalse(worldManager.getUnloadedWorld("World2").isDefined)
148+
assertFalse(File(Bukkit.getWorldContainer(), "World2").isDirectory)
149+
136150
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldDeleteEvent::class.java))
137151
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldUnloadedEvent::class.java))
138152
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldRemovedEvent::class.java))
@@ -143,21 +157,21 @@ class WorldManagerTest : TestWithMockBukkit() {
143157
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2).saveBukkitWorld(true)).isSuccess)
144158
assertFalse(world2.isLoaded)
145159
assertFalse(world2.bukkitWorld.isDefined)
146-
assertFalse(worldManager.getLoadedWorld("world2").isDefined)
147-
assertTrue(worldManager.getWorld("world2").isDefined)
148-
assertTrue(worldManager.getUnloadedWorld("world2").isDefined)
160+
assertFalse(worldManager.getLoadedWorld("World2").isDefined)
161+
assertTrue(worldManager.getWorld("World2").isDefined)
162+
assertTrue(worldManager.getUnloadedWorld("World2").isDefined)
149163

150164
assertTrue(worldManager.loadWorld(LoadWorldOptions.world(world2)).isSuccess)
151165
assertTrue(world2.isLoaded)
152-
assertTrue(worldManager.getLoadedWorld("world2").flatMap{ w -> w.bukkitWorld }.isDefined)
153-
assertTrue(worldManager.getLoadedWorld("world2").isDefined)
154-
assertFalse(worldManager.getUnloadedWorld("world2").isDefined)
166+
assertTrue(worldManager.getLoadedWorld("World2").flatMap{ w -> w.bukkitWorld }.isDefined)
167+
assertTrue(worldManager.getLoadedWorld("World2").isDefined)
168+
assertFalse(worldManager.getUnloadedWorld("World2").isDefined)
155169
}
156170

157171
@Test
158172
fun `Load world failed - invalid world folder`() {
159173
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2)).isSuccess)
160-
File(Bukkit.getWorldContainer(), "world2/").deleteRecursively()
174+
File(Bukkit.getWorldContainer(), "World2/").deleteRecursively()
161175
assertEquals(
162176
LoadFailureReason.WORLD_FOLDER_INVALID,
163177
worldManager.loadWorld(LoadWorldOptions.world(world2)).failureReason
@@ -190,7 +204,7 @@ class WorldManagerTest : TestWithMockBukkit() {
190204
.seed(4321L)
191205
).isSuccess)
192206

193-
val getWorld = worldManager.getLoadedWorld("world2")
207+
val getWorld = worldManager.getLoadedWorld("World2")
194208
assertTrue(getWorld.isDefined)
195209
val world = getWorld.get()
196210
assertNotNull(world)
@@ -230,7 +244,7 @@ class WorldManagerTest : TestWithMockBukkit() {
230244
fun `Clone world failed - target world exists and loaded`() {
231245
assertEquals(
232246
CloneFailureReason.WORLD_EXIST_LOADED,
233-
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "world2")).failureReason
247+
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "World2")).failureReason
234248
)
235249
}
236250

@@ -239,7 +253,7 @@ class WorldManagerTest : TestWithMockBukkit() {
239253
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2)).isSuccess)
240254
assertEquals(
241255
CloneFailureReason.WORLD_EXIST_UNLOADED,
242-
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "world2")).failureReason
256+
worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "World2")).failureReason
243257
)
244258
}
245259

0 commit comments

Comments
 (0)