WorldGuard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java

440 lines
14 KiB
Java

/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldGuard team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldguard.protection.managers;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Sets;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.RegionResultSet;
import com.sk89q.worldguard.protection.flags.registry.FlagRegistry;
import com.sk89q.worldguard.protection.managers.index.ConcurrentRegionIndex;
import com.sk89q.worldguard.protection.managers.index.RegionIndex;
import com.sk89q.worldguard.protection.managers.storage.DifferenceSaveException;
import com.sk89q.worldguard.protection.managers.storage.RegionDatabase;
import com.sk89q.worldguard.protection.managers.storage.StorageException;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import com.sk89q.worldguard.protection.regions.RegionIdentifier;
import com.sk89q.worldguard.protection.util.RegionCollectionConsumer;
import com.sk89q.worldguard.util.Normal;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
/**
* A region manager holds the regions for a world.
*/
public final class RegionManager {
private final RegionDatabase store;
private final Function<String, ? extends ConcurrentRegionIndex> indexFactory;
private final FlagRegistry flagRegistry;
private ConcurrentRegionIndex index;
/**
* Create a new index.
*
* @param store the region store
* @param indexFactory the factory for creating new instances of the index
* @param flagRegistry the flag registry
*/
public RegionManager(RegionDatabase store, Function<String, ? extends ConcurrentRegionIndex> indexFactory, FlagRegistry flagRegistry) {
checkNotNull(store);
checkNotNull(indexFactory);
checkNotNull(flagRegistry, "flagRegistry");
this.store = store;
this.indexFactory = indexFactory;
this.index = indexFactory.apply(store.getName());
this.flagRegistry = flagRegistry;
}
/**
* Get a displayable name for this store.
*/
public String getName() {
return store.getName();
}
/**
* Load regions from storage and replace the index on this manager with
* the regions loaded from the store.
*
* <p>This method will block until the save completes, but it will
* not block access to the region data from other threads, nor will it
* prevent the creation or modification of regions in the index while
* a new collection of regions is loaded from storage.</p>
*
* @throws StorageException thrown when loading fails
*/
public void load() throws StorageException {
Set<ProtectedRegion> regions = store.loadAll(flagRegistry);
for (ProtectedRegion region : regions) {
region.setDirty(false);
}
setRegions(regions);
}
/**
* Save a snapshot of all the regions as it is right now to storage.
*
* @throws StorageException thrown on save error
*/
public void save() throws StorageException {
index.setDirty(false);
store.saveAll(new HashSet<>(getFilteredValuesCopy()));
}
/**
* Save changes to the region index to disk, preferring to only save
* the changes (rather than the whole index), but choosing to save the
* whole index if the underlying store does not support partial saves.
*
* <p>This method does nothing if there are no changes.</p>
*
* @return true if there were changes to be saved
* @throws StorageException thrown on save error
*/
public boolean saveChanges() throws StorageException {
RegionDifference diff = index.getAndClearDifference();
boolean successful = false;
try {
if (diff.containsChanges()) {
try {
store.saveChanges(diff);
} catch (DifferenceSaveException e) {
save(); // Partial save is not supported
}
successful = true;
return true;
} else {
successful = true;
return false;
}
} finally {
if (!successful) {
index.setDirty(diff);
}
}
}
/**
* Load the regions for a chunk.
*
* @param position the position
*/
public void loadChunk(BlockVector2 position) {
index.bias(position);
}
/**
* Load the regions for a chunk.
*
* @param positions a collection of positions
*/
public void loadChunks(Collection<BlockVector2> positions) {
index.biasAll(positions);
}
/**
* Unload the regions for a chunk.
*
* @param position the position
*/
public void unloadChunk(BlockVector2 position) {
index.forget(position);
}
/**
* Get an unmodifiable map of regions containing the state of the
* index at the time of call.
*
* <p>This call is relatively heavy (and may block other threads),
* so refrain from calling it frequently.</p>
*
* @return a map of regions
*/
public Map<String, ProtectedRegion> getRegions() {
Map<String, ProtectedRegion> map = new HashMap<>();
for (ProtectedRegion region : index.values()) {
map.put(Normal.normalize(region.getId()), region);
}
return Collections.unmodifiableMap(map);
}
/**
* Replace the index with the regions in the given map.
*
* <p>The parents of the regions will also be added to the index, even
* if they are not in the provided map.</p>
*
* @param regions a map of regions
*/
public void setRegions(Map<String, ProtectedRegion> regions) {
checkNotNull(regions);
setRegions(regions.values());
}
/**
* Replace the index with the regions in the given collection.
*
* <p>The parents of the regions will also be added to the index, even
* if they are not in the provided map.</p>
*
* @param regions a collection of regions
*/
public void setRegions(Collection<ProtectedRegion> regions) {
checkNotNull(regions);
ConcurrentRegionIndex newIndex = indexFactory.apply(getName());
newIndex.addAll(regions);
newIndex.getAndClearDifference(); // Clear changes
this.index = newIndex;
}
/**
* Aad a region to the manager.
*
* <p>The parents of the region will also be added to the index.</p>
*
* @param region the region
*/
public void addRegion(ProtectedRegion region) {
checkNotNull(region);
index.add(region);
}
/**
* Return whether the index contains a region by the given name,
* with equality determined by {@link Normal}.
*
* @param id the name of the region
* @return true if this index contains the region
*/
@Deprecated
public boolean hasRegion(String id) {
return index.contains(id);
}
/**
* Return whether the index contains a region with the given identifier,
* with quality determined by {@link Normal}.
*
* @param id the region identifier
* @return true if this index contains the region
*/
public boolean hasRegion(RegionIdentifier id) {
return index.contains(id.getLegacyQualifiedName());
}
/**
* Get the region named by the given name (equality determined using
* {@link Normal}).
*
* @param id the name of the region
* @return a region or {@code null}
*/
@Nullable
@Deprecated
public ProtectedRegion getRegion(String id) {
checkNotNull(id);
return index.get(id);
}
/**
* Get the region named y the given id.
*
* @param id the region identifier
* @return a region or {@code null}
*/
@Nullable
public ProtectedRegion getRegion(RegionIdentifier id) {
checkNotNull(id);
return index.get(id.getLegacyQualifiedName());
}
/**
* @deprecated Use exact ids with {@link #getRegion}
*/
@Nullable
@Deprecated
public ProtectedRegion matchRegion(String pattern) {
return getRegion(pattern);
}
/**
* Remove a region from the index with the given name, opting to remove
* the children of the removed region.
*
* @param id the name of the region
* @return a list of removed regions where the first entry is the region specified by {@code id}
*/
@Nullable
public Set<ProtectedRegion> removeRegion(String id) {
return removeRegion(id, RemovalStrategy.REMOVE_CHILDREN);
}
/**
* Remove a region from the index with the given name.
*
* @param id the name of the region
* @param strategy what to do with children
* @return a list of removed regions where the first entry is the region specified by {@code id}
*/
@Nullable
public Set<ProtectedRegion> removeRegion(String id, RemovalStrategy strategy) {
return index.remove(id, strategy);
}
/**
* Query for effective flags and owners for the given positive.
*
* @param position the position
* @return the query object
*/
public ApplicableRegionSet getApplicableRegions(BlockVector3 position) {
checkNotNull(position);
Set<ProtectedRegion> regions = Sets.newHashSet();
index.applyContaining(position, new RegionCollectionConsumer(regions, true));
return new RegionResultSet(regions, index.get("__global__"));
}
/**
* Query for effective flags and owners for the area represented
* by the given region.
*
* @param region the region
* @return the query object
*/
public ApplicableRegionSet getApplicableRegions(ProtectedRegion region) {
checkNotNull(region);
Set<ProtectedRegion> regions = Sets.newHashSet();
index.applyIntersecting(region, new RegionCollectionConsumer(regions, true));
return new RegionResultSet(regions, index.get("__global__"));
}
/**
* Get a list of region names for regions that contain the given position.
*
* @param position the position
* @return a list of names
*/
public List<String> getApplicableRegionsIDs(BlockVector3 position) {
checkNotNull(position);
final List<String> names = new ArrayList<>();
index.applyContaining(position, region -> names.add(region.getId()));
return names;
}
/**
* Return whether there are any regions intersecting the given region that
* are not owned by the given player.
*
* @param region the region
* @param player the player
* @return true if there are such intersecting regions
*/
public boolean overlapsUnownedRegion(ProtectedRegion region, final LocalPlayer player) {
checkNotNull(region);
checkNotNull(player);
RegionIndex index = this.index;
final AtomicBoolean overlapsUnowned = new AtomicBoolean();
index.applyIntersecting(region, test -> {
if (!test.getOwners().contains(player)) {
overlapsUnowned.set(true);
return false;
} else {
return true;
}
});
return overlapsUnowned.get();
}
/**
* Get the number of regions.
*
* @return the number of regions
*/
public int size() {
return index.size();
}
/**
* Get the number of regions that are owned by the given player.
*
* @param player the player
* @return name number of regions that a player owns
*/
public int getRegionCountOfPlayer(final LocalPlayer player) {
checkNotNull(player);
final AtomicInteger count = new AtomicInteger();
index.apply(test -> {
if (test.getOwners().contains(player)) {
count.incrementAndGet();
}
return true;
});
return count.get();
}
/**
* Get an {@link ArrayList} copy of regions in the index with transient regions filtered.
*
* @return a list
*/
private List<ProtectedRegion> getFilteredValuesCopy() {
List<ProtectedRegion> filteredValues = new ArrayList<>();
for (ProtectedRegion region : index.values()) {
if (!region.isTransient()) {
filteredValues.add(region);
}
}
return filteredValues;
}
}