001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.bukkit.util; 020 021import com.google.common.collect.Maps; 022import com.plotsquared.core.location.World; 023import net.kyori.adventure.key.Key; 024import org.bukkit.Bukkit; 025import org.bukkit.NamespacedKey; 026import org.checkerframework.checker.nullness.qual.NonNull; 027import org.jetbrains.annotations.NotNull; 028 029import java.lang.ref.SoftReference; 030import java.lang.ref.WeakReference; 031import java.nio.file.Path; 032import java.util.Map; 033 034public class BukkitWorld implements World<org.bukkit.World> { 035 036 // Upon world unload we should probably have the P2 BukkitWorld be GCed relatively eagerly, thus freeing the bukkit world. 037 // We also want to avoid circumstances where a bukkit world has been GCed, but a P2 BukkitWorld has not 038 private static final Map<String, WeakReference<BukkitWorld>> worldMap = Maps.newHashMap(); 039 private static final boolean HAS_MIN_Y; 040 041 static { 042 boolean temp; 043 try { 044 org.bukkit.World.class.getMethod("getMinHeight"); 045 temp = true; 046 } catch (NoSuchMethodException e) { 047 temp = false; 048 } 049 HAS_MIN_Y = temp; 050 } 051 052 // We want to allow GC to remove bukkit worlds, but not too eagerly 053 private final SoftReference<org.bukkit.World> world; 054 private Key key = null; 055 056 private BukkitWorld(final org.bukkit.World world) { 057 this.world = new SoftReference<>(world); 058 } 059 060 /** 061 * Get a new {@link BukkitWorld} from a world name 062 * 063 * @param worldName World name 064 * @return World instance 065 */ 066 public static @NonNull BukkitWorld of(final @NonNull String worldName) { 067 final org.bukkit.World bukkitWorld = Bukkit.getWorld(worldName); 068 if (bukkitWorld == null) { 069 throw new IllegalArgumentException(String.format("There is no world with the name '%s'", worldName)); 070 } 071 return of(bukkitWorld); 072 } 073 074 /** 075 * Get a new {@link BukkitWorld} from a Bukkit world 076 * 077 * @param world Bukkit world 078 * @return World instance 079 */ 080 public static @NonNull BukkitWorld of(final org.bukkit.World world) { 081 WeakReference<BukkitWorld> bukkitWorldRef = worldMap.get(world.getName()); 082 BukkitWorld bukkitWorld; 083 if (bukkitWorldRef != null && (bukkitWorld = bukkitWorldRef.get()) != null && world.equals(bukkitWorld.world.get())) { 084 return bukkitWorld; 085 } 086 bukkitWorld = new BukkitWorld(world); 087 worldMap.put(world.getName(), new WeakReference<>(bukkitWorld)); 088 return bukkitWorld; 089 } 090 091 /** 092 * Get the min world height from a Bukkit {@link org.bukkit.World}. Inclusive 093 * 094 * @since 6.6.0 095 */ 096 public static int getMinWorldHeight(org.bukkit.World world) { 097 return HAS_MIN_Y ? world.getMinHeight() : 0; 098 } 099 100 /** 101 * Get the max world height from a Bukkit {@link org.bukkit.World}. Exclusive 102 * 103 * @since 6.6.0 104 */ 105 public static int getMaxWorldHeight(org.bukkit.World world) { 106 return HAS_MIN_Y ? world.getMaxHeight() : 256; 107 } 108 109 @Override 110 public org.bukkit.@NonNull World getPlatformWorld() { 111 org.bukkit.World world = this.world.get(); 112 if (world == null) { 113 throw new IllegalStateException("Bukkit platform world was unloaded from memory"); 114 } 115 return world; 116 } 117 118 @Override 119 public @NonNull String getName() { 120 return this.getPlatformWorld().getName(); 121 } 122 123 @Override 124 public int getMinHeight() { 125 return getMinWorldHeight(getPlatformWorld()); 126 } 127 128 @Override 129 public int getMaxHeight() { 130 return getMaxWorldHeight(getPlatformWorld()) - 1; 131 } 132 133 @Override 134 public @NonNull Path getWorldFolder() { 135 return getPlatformWorld().getWorldFolder().toPath(); 136 } 137 138 @Override 139 @SuppressWarnings("PatternValidation") // kyori pattern validation... 140 public @NotNull Key key() { 141 if (this.key == null) { 142 NamespacedKey bukkitKey = getPlatformWorld().getKey(); 143 this.key = Key.key(bukkitKey.getNamespace(), bukkitKey.getKey()); 144 } 145 return this.key; 146 } 147 148 @Override 149 public boolean equals(final Object o) { 150 if (this == o) { 151 return true; 152 } 153 if (o == null || getClass() != o.getClass()) { 154 return false; 155 } 156 final BukkitWorld that = (BukkitWorld) o; 157 return world.equals(that.world); 158 } 159 160 @Override 161 public int hashCode() { 162 return world.hashCode(); 163 } 164 165 public String toString() { 166 return "BukkitWorld(world=" + this.world + ")"; 167 } 168 169}