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 org.bukkit.Bukkit;
024import org.checkerframework.checker.nullness.qual.NonNull;
025
026import java.lang.ref.SoftReference;
027import java.lang.ref.WeakReference;
028import java.util.Map;
029
030public class BukkitWorld implements World<org.bukkit.World> {
031
032    // Upon world unload we should probably have the P2 BukkitWorld be GCed relatively eagerly, thus freeing the bukkit world.
033    //  We also want to avoid circumstances where a bukkit world has been GCed, but a P2 BukkitWorld has not
034    private static final Map<String, WeakReference<BukkitWorld>> worldMap = Maps.newHashMap();
035    private static final boolean HAS_MIN_Y;
036
037    static {
038        boolean temp;
039        try {
040            org.bukkit.World.class.getMethod("getMinHeight");
041            temp = true;
042        } catch (NoSuchMethodException e) {
043            temp = false;
044        }
045        HAS_MIN_Y = temp;
046    }
047
048    // We want to allow GC to remove bukkit worlds, but not too eagerly
049    private final SoftReference<org.bukkit.World> world;
050
051    private BukkitWorld(final org.bukkit.World world) {
052        this.world = new SoftReference<>(world);
053    }
054
055    /**
056     * Get a new {@link BukkitWorld} from a world name
057     *
058     * @param worldName World name
059     * @return World instance
060     */
061    public static @NonNull BukkitWorld of(final @NonNull String worldName) {
062        final org.bukkit.World bukkitWorld = Bukkit.getWorld(worldName);
063        if (bukkitWorld == null) {
064            throw new IllegalArgumentException(String.format("There is no world with the name '%s'", worldName));
065        }
066        return of(bukkitWorld);
067    }
068
069    /**
070     * Get a new {@link BukkitWorld} from a Bukkit world
071     *
072     * @param world Bukkit world
073     * @return World instance
074     */
075    public static @NonNull BukkitWorld of(final org.bukkit.World world) {
076        WeakReference<BukkitWorld> bukkitWorldRef = worldMap.get(world.getName());
077        BukkitWorld bukkitWorld;
078        if (bukkitWorldRef != null && (bukkitWorld = bukkitWorldRef.get()) != null && world.equals(bukkitWorld.world.get())) {
079            return bukkitWorld;
080        }
081        bukkitWorld = new BukkitWorld(world);
082        worldMap.put(world.getName(), new WeakReference<>(bukkitWorld));
083        return bukkitWorld;
084    }
085
086    /**
087     * Get the min world height from a Bukkit {@link org.bukkit.World}. Inclusive
088     *
089     * @since 6.6.0
090     */
091    public static int getMinWorldHeight(org.bukkit.World world) {
092        return HAS_MIN_Y ? world.getMinHeight() : 0;
093    }
094
095    /**
096     * Get the max world height from a Bukkit {@link org.bukkit.World}. Exclusive
097     *
098     * @since 6.6.0
099     */
100    public static int getMaxWorldHeight(org.bukkit.World world) {
101        return HAS_MIN_Y ? world.getMaxHeight() : 256;
102    }
103
104    @Override
105    public org.bukkit.World getPlatformWorld() {
106        org.bukkit.World world = this.world.get();
107        if (world == null) {
108            throw new IllegalStateException("Bukkit platform world was unloaded from memory");
109        }
110        return world;
111    }
112
113    @Override
114    public @NonNull String getName() {
115        return this.getPlatformWorld().getName();
116    }
117
118    @Override
119    public int getMinHeight() {
120        return getMinWorldHeight(getPlatformWorld());
121    }
122
123    @Override
124    public int getMaxHeight() {
125        return getMaxWorldHeight(getPlatformWorld()) - 1;
126    }
127
128    @Override
129    public boolean equals(final Object o) {
130        if (this == o) {
131            return true;
132        }
133        if (o == null || getClass() != o.getClass()) {
134            return false;
135        }
136        final BukkitWorld that = (BukkitWorld) o;
137        return world.equals(that.world);
138    }
139
140    @Override
141    public int hashCode() {
142        return world.hashCode();
143    }
144
145    public String toString() {
146        return "BukkitWorld(world=" + this.world + ")";
147    }
148
149}