/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.internal.regions; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.jcwhatever.nucleus.Nucleus; import com.jcwhatever.nucleus.collections.ElementCounter; import com.jcwhatever.nucleus.collections.ElementCounter.RemovalPolicy; import com.jcwhatever.nucleus.internal.NucMsg; import com.jcwhatever.nucleus.regions.IGlobalRegionManager; import com.jcwhatever.nucleus.regions.IRegion; import com.jcwhatever.nucleus.regions.ReadOnlyRegion; import com.jcwhatever.nucleus.regions.options.RegionEventPriority.PriorityType; import com.jcwhatever.nucleus.utils.CollectionUtils; import com.jcwhatever.nucleus.utils.MetaKey; import com.jcwhatever.nucleus.utils.PreCon; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Global Region Manager. * * <p>Tracks instances of {@link IRegion} and provides methods to determine which regions * a player is in as well as track players to determine when they enter and leave * player watcher regions.</p> * * <p>Methods that return {@link IRegion} instances are returning * {@link com.jcwhatever.nucleus.regions.ReadOnlyRegion} instances. This is to prevent * inter-plugin conflicts caused by changes to a region that the region owning plugin is * unaware of.</p> * * <p>Methods that return a specific region type return the actual region instance.</p> */ public final class InternalRegionManager extends RegionTypeManager<IRegion> implements IGlobalRegionManager { public static final MetaKey<IRegion> REGION_HANDLE = new MetaKey<IRegion>(IRegion.class); // worlds that have regions private final ElementCounter<World> _listenerWorlds = new ElementCounter<>(RemovalPolicy.REMOVE); // store managers for individual region types. private final Map<Class<? extends IRegion>, RegionTypeManager<?>> _managers = new HashMap<>(15); // store regions by lookup name. lookup name is: PluginName:RegionName private final Multimap<String, IRegion> _regionNameMap = MultimapBuilder.hashKeys(35).hashSetValues(5).build(); // watch region and players to detect when players enter/leave regions private final InternalPlayerWatcher _playerWatcher = new InternalPlayerWatcher(this); /** * Constructor * * @param plugin The owning plugin. */ public InternalRegionManager(Plugin plugin) { super(IRegion.class); if (Nucleus.getPlugin() != plugin) { throw new RuntimeException("InternalRegionManager should not be instantiated."); } } /** * Get the internal player watcher to notify it of changes * in the players position. */ public InternalPlayerWatcher getPlayerWatcher() { return _playerWatcher; } @Override public <T extends IRegion> boolean hasRegion(Location location, Class<T> regionClass) { RegionTypeManager<T> manager = getManager(regionClass, false); return manager != null && manager.hasRegion(location); } @Override public <T extends IRegion> boolean hasRegion(World world, int x, int y, int z, Class<T> regionClass) { RegionTypeManager<T> manager = getManager(regionClass, false); return manager != null && manager.hasRegion(world, x, y, z); } @Nullable @Override public List<IRegion> getRegions(Plugin plugin, String name) { return new ArrayList<>(_regionNameMap.get(getLookupName(plugin, name))); } @Override public <T extends IRegion> List<T> getRegions(Location location, Class<T> regionClass) { PreCon.notNull(location); PreCon.notNull(regionClass); RegionTypeManager<T> manager = getManager(regionClass, false); if (manager == null) return CollectionUtils.unmodifiableList(); return manager.getRegions(location); } @Override public <T extends IRegion> List<T> getRegions(World world, int x, int y, int z, Class<T> regionClass) { PreCon.notNull(world); PreCon.notNull(regionClass); RegionTypeManager<T> manager = getManager(regionClass, false); if (manager == null) return CollectionUtils.unmodifiableList(); return manager.getRegions(world, x, y, z); } @Override public <T extends IRegion> List<T> getListenerRegions(Location location, Class<T> regionClass) { PreCon.notNull(location); PreCon.notNull(regionClass); RegionTypeManager<T> manager = getManager(regionClass, false); if (manager == null) return CollectionUtils.unmodifiableList(); return manager.getListenerRegions(location); } @Override public <T extends IRegion> List<T> getListenerRegions(World world, int x, int y, int z, Class<T> regionClass) { PreCon.notNull(world); PreCon.notNull(regionClass); RegionTypeManager<T> manager = getManager(regionClass, false); if (manager == null) return CollectionUtils.unmodifiableList(); return manager.getListenerRegions(world, x, y, z); } @Override public <T extends IRegion> List<T> getListenerRegions(Location location, PriorityType priorityType, Class<T> regionClass) { PreCon.notNull(location); PreCon.notNull(priorityType); PreCon.notNull(regionClass); RegionTypeManager<T> manager = getManager(regionClass, false); if (manager == null) return CollectionUtils.unmodifiableList(); return manager.getListenerRegions(location, priorityType); } @Override public <T extends IRegion> List<T> getListenerRegions(World world, int x, int y, int z, PriorityType priorityType, Class<T> regionClass) { PreCon.notNull(world); PreCon.notNull(priorityType); PreCon.notNull(regionClass); RegionTypeManager<T> manager = getManager(regionClass, false); if (manager == null) return CollectionUtils.unmodifiableList(); return manager.getListenerRegions(world, x, y, z, priorityType); } @Override public <T extends IRegion> List<T> getRegionsInChunk(Chunk chunk, Class<T> regionClass) { PreCon.notNull(chunk); PreCon.notNull(regionClass); RegionTypeManager<T> manager = getManager(regionClass, false); if (manager == null) return CollectionUtils.unmodifiableList(); return manager.getRegionsInChunk(chunk); } @Override public <T extends IRegion> List<T> getRegionsInChunk(World world, int x, int z, Class<T> regionClass) { PreCon.notNull(world); PreCon.notNull(regionClass); RegionTypeManager<T> manager = getManager(regionClass, false); if (manager == null) return CollectionUtils.unmodifiableList(); return manager.getRegionsInChunk(world, x, z); } @Override public List<IRegion> getPlayerRegions(Player player) { PreCon.notNull(player); synchronized(_playerWatcher) { Set<IRegion> regions = _playerWatcher.getCurrentRegions(player.getUniqueId()); if (regions == null) return new ArrayList<>(0); return new ArrayList<>(regions); } } @Override public void forgetPlayer(Player p, IRegion region) { PreCon.notNull(p); PreCon.notNull(region); synchronized(_playerWatcher) { Set<IRegion> regions = _playerWatcher.getCurrentRegions(p.getUniqueId()); if (regions == null) return; regions.remove(new ReadOnlyRegion(region)); } } @Override public void register(IRegion region) { PreCon.notNull(region); if (region.isDisposed()) { NucMsg.debug("Failed to register region '{0}' from plugin '{1}' with RegionManager because " + "it is disposed.", region.getName(), region.getPlugin().getName()); return; } if (!region.isDefined() || !region.isWorldLoaded()) { NucMsg.debug("Failed to register region '{0}' with RegionManager because " + "it's coords are undefined. Region Type: {1}", region.getName(), region.getClass().getName()); return; } if (region instanceof ReadOnlyRegion) { region = region.getMeta().get(REGION_HANDLE); if (region == null) { throw new RuntimeException("ReadOnlyRegions handle has no meta reference to itself."); } } @SuppressWarnings("unchecked") RegionTypeManager<IRegion> manager = (RegionTypeManager<IRegion>)getManager(region.getClass(), true); assert manager != null; manager.register(region); ReadOnlyRegion readOnlyRegion = new ReadOnlyRegion(region); super.register(readOnlyRegion); _regionNameMap.put(getLookupName(region.getPlugin(), region), readOnlyRegion); } @Override public void unregister(IRegion region) { PreCon.notNull(region); if (!region.isDefined() || !region.isWorldLoaded()) return; @SuppressWarnings("unchecked") RegionTypeManager<IRegion> manager = (RegionTypeManager<IRegion>)getManager(region.getClass(), false); if (manager != null) { manager.unregister(region); } ReadOnlyRegion readOnlyRegion = new ReadOnlyRegion(region); super.unregister(readOnlyRegion); _regionNameMap.remove(getLookupName(region.getPlugin(), region), readOnlyRegion); } @Override protected void onRegister(IRegion region, boolean isFormerListener) { if (region.isEventListener()) { //noinspection ConstantConditions _listenerWorlds.add(region.getWorld()); } else if (isFormerListener){ //noinspection ConstantConditions _listenerWorlds.subtract(region.getWorld()); } } @Override protected void onUnregister(IRegion region) { if (region.isEventListener()) { //noinspection ConstantConditions _listenerWorlds.subtract(region.getWorld()); } } /** * Get direct reference to region event listener world counter. * * <p>Contains the worlds that contain event listening regions and * the number of event listening regions in each of those worlds.</p> * */ ElementCounter<World> getListenerWorlds() { return _listenerWorlds; } /* * Remove a region from a region map. */ private <T extends Set<IRegion>> boolean removeFromMap(Map<String, T> map, String key, ReadOnlyRegion region) { Set<IRegion> regions = map.get(key); return regions != null && regions.remove(region); } /* * Get region manager for a specific region type. */ @Nullable private <T extends IRegion> RegionTypeManager<T> getManager(Class<T> regionClass, boolean create) { @SuppressWarnings("unchecked") RegionTypeManager<T> manager = (RegionTypeManager<T>)_managers.get(regionClass); if (manager == null && create) { manager = new RegionTypeManager<>(regionClass); _managers.put(regionClass, manager); } return manager; } /* * Get a regions lookup name. */ private String getLookupName(Plugin plugin, IRegion region) { return plugin.getName() + ':' + region.getSearchName(); } /* * Get a regions lookup name. */ private String getLookupName(Plugin plugin, String name) { return plugin.getName() + ':' + name.toLowerCase(); } }