diff --git a/src/main/java/com/sk89q/worldguard/LocalPlayer.java b/src/main/java/com/sk89q/worldguard/LocalPlayer.java index b1964aa9..585acd2f 100644 --- a/src/main/java/com/sk89q/worldguard/LocalPlayer.java +++ b/src/main/java/com/sk89q/worldguard/LocalPlayer.java @@ -20,10 +20,13 @@ package com.sk89q.worldguard; import com.sk89q.worldedit.Vector; +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; import java.util.UUID; -public abstract class LocalPlayer { +public abstract class LocalPlayer implements RegionAssociable { /** * Get this player's name. @@ -89,7 +92,18 @@ public abstract class LocalPlayer { * @return Whether this player has {@code perm} */ public abstract boolean hasPermission(String perm); - + + @Override + public Association getAssociation(ProtectedRegion region) { + if (region.isOwner(this)) { + return Association.OWNER; + } else if (region.isMember(this)) { + return Association.MEMBER; + } else { + return Association.NON_MEMBER; + } + } + @Override public boolean equals(Object obj) { return obj instanceof LocalPlayer && ((LocalPlayer) obj).getName().equals(getName()); diff --git a/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java b/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java index 5fafb41a..904a1a10 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java @@ -22,6 +22,7 @@ import com.sk89q.worldguard.LocalPlayer; import com.sk89q.worldguard.protection.ApplicableRegionSet; import com.sk89q.worldguard.protection.GlobalRegionManager; +import com.sk89q.worldguard.protection.association.RegionAssociable; import com.sk89q.worldguard.protection.flags.DefaultFlag; import com.sk89q.worldguard.protection.flags.Flag; import com.sk89q.worldguard.protection.flags.StateFlag; @@ -108,26 +109,46 @@ public ApplicableRegionSet getApplicableRegions(Location location) { * @param player the player * @param flags zero or more flags * @return true if permission is granted - * @see ApplicableRegionSet#testBuild(LocalPlayer, StateFlag...) + * @see ApplicableRegionSet#testBuild(RegionAssociable, StateFlag...) */ public boolean testBuild(Location location, Player player, StateFlag... flags) { checkNotNull(location); checkNotNull(player); - checkNotNull(flags); World world = location.getWorld(); - WorldConfiguration worldConfig = config.get(world); - - if (!worldConfig.useRegions) { - return true; - } if (player.hasPermission("worldguard.region.bypass." + world.getName())) { return true; } LocalPlayer localPlayer = plugin.wrapPlayer(player); - return getApplicableRegions(location).testBuild(localPlayer, flags); + return testBuild(location, localPlayer, flags); + } + + /** + * Test whether the given flags evaluate to {@code ALLOW}, implicitly also + * considering the {@link DefaultFlag#BUILD} flag. + * + *

This method is equivalent to calling + * {@link #testState(Location, Player, StateFlag...)} with + * {@code flags} plus the {@code BUILD} flag.

+ * + * @param location the location + * @param subject the subject + * @param flags zero or more flags + * @return true if permission is granted + * @see ApplicableRegionSet#testBuild(RegionAssociable, StateFlag...) + */ + public boolean testBuild(Location location, RegionAssociable subject, StateFlag... flags) { + checkNotNull(location); + checkNotNull(subject); + checkNotNull(flags); + + World world = location.getWorld(); + WorldConfiguration worldConfig = config.get(world); + + return !worldConfig.useRegions || getApplicableRegions(location).testBuild(subject, flags); + } /** @@ -144,7 +165,7 @@ public boolean testBuild(Location location, Player player, StateFlag... flags) { * @param player an optional player, which would be used to determine the region group to apply * @param flag the flag * @return true if the result was {@code ALLOW} - * @see ApplicableRegionSet#queryValue(LocalPlayer, Flag) + * @see ApplicableRegionSet#queryValue(RegionAssociable, Flag) */ public boolean testState(Location location, @Nullable Player player, StateFlag... flag) { return StateFlag.test(queryState(location, player, flag)); @@ -165,7 +186,7 @@ public boolean testState(Location location, @Nullable Player player, StateFlag.. * @param player an optional player, which would be used to determine the region groups that apply * @param flags a list of flags to check * @return a state - * @see ApplicableRegionSet#queryState(LocalPlayer, StateFlag...) + * @see ApplicableRegionSet#queryState(RegionAssociable, StateFlag...) */ @Nullable public State queryState(Location location, @Nullable Player player, StateFlag... flags) { @@ -195,7 +216,7 @@ public State queryState(Location location, @Nullable Player player, StateFlag... * @param player an optional player, which would be used to determine the region group to apply * @param flag the flag * @return a value, which could be {@code null} - * @see ApplicableRegionSet#queryValue(LocalPlayer, Flag) + * @see ApplicableRegionSet#queryValue(RegionAssociable, Flag) */ @Nullable public V queryValue(Location location, @Nullable Player player, Flag flag) { @@ -218,7 +239,7 @@ public V queryValue(Location location, @Nullable Player player, Flag flag * @param player an optional player, which would be used to determine the region group to apply * @param flag the flag * @return a collection of values - * @see ApplicableRegionSet#queryAllValues(LocalPlayer, Flag) + * @see ApplicableRegionSet#queryAllValues(RegionAssociable, Flag) */ public Collection queryAllValues(Location location, @Nullable Player player, Flag flag) { LocalPlayer localPlayer = player != null ? plugin.wrapPlayer(player) : null; diff --git a/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java b/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java index 1047fa2e..c32ed3b5 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java @@ -71,6 +71,15 @@ public boolean isKnown() { return !causes.isEmpty(); } + @Nullable + public Object getRootCause() { + if (!causes.isEmpty()) { + return causes.get(0); + } + + return null; + } + @Nullable public Player getPlayerRootCause() { for (Object object : causes) { diff --git a/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java b/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java index 0f1a9134..c259809f 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java @@ -19,6 +19,7 @@ package com.sk89q.worldguard.bukkit.listener; +import com.google.common.collect.Lists; import com.sk89q.worldguard.bukkit.WorldGuardPlugin; import com.sk89q.worldguard.bukkit.cause.Cause; import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent; @@ -28,6 +29,7 @@ import com.sk89q.worldguard.bukkit.event.entity.SpawnEntityEvent; import com.sk89q.worldguard.bukkit.event.entity.UseEntityEvent; import com.sk89q.worldguard.bukkit.event.inventory.UseItemEvent; +import com.sk89q.worldguard.bukkit.util.BlockStateAsBlockFunction; import com.sk89q.worldguard.bukkit.util.Blocks; import com.sk89q.worldguard.bukkit.util.Events; import com.sk89q.worldguard.bukkit.util.Materials; @@ -40,6 +42,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.Item; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.ThrownPotion; @@ -84,11 +87,14 @@ import org.bukkit.event.player.PlayerUnleashEntityEvent; import org.bukkit.event.vehicle.VehicleDamageEvent; import org.bukkit.event.vehicle.VehicleDestroyEvent; +import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.material.Dispenser; import org.bukkit.material.MaterialData; +import org.bukkit.util.Vector; import javax.annotation.Nullable; +import java.util.List; import static com.sk89q.worldguard.bukkit.cause.Cause.create; import static com.sk89q.worldguard.bukkit.util.Materials.isBlockModifiedOnClick; @@ -138,6 +144,12 @@ public void onBlockBurn(BlockBurnEvent event) { Events.fireToCancel(event, new UseBlockEvent(event, Cause.unknown(), event.getBlock())); } + @EventHandler + public void onStructureGrowEvent(StructureGrowEvent event) { + List blockList = Lists.transform(event.getBlocks(), new BlockStateAsBlockFunction()); + Events.fireBulkEventToCancel(event, new PlaceBlockEvent(event, create(event.getPlayer()), event.getLocation().getWorld(), blockList, Material.AIR)); + } + // TODO: Handle EntityCreatePortalEvent? @EventHandler @@ -162,9 +174,20 @@ public void onEntityChangeBlock(EntityChangeBlockEvent event) { Events.fireToCancel(event, new BreakBlockEvent(event, create(entity), event.getBlock())); } } else { + boolean wasCancelled = event.isCancelled(); Cause cause = create(entity); Events.fireToCancel(event, new PlaceBlockEvent(event, cause, event.getBlock().getLocation(), to)); + + if (event.isCancelled() && !wasCancelled && entity instanceof FallingBlock) { + FallingBlock fallingBlock = (FallingBlock) entity; + ItemStack itemStack = new ItemStack(fallingBlock.getMaterial(), 1, fallingBlock.getBlockData()); + Item item = block.getWorld().dropItem(fallingBlock.getLocation(), itemStack); + item.setVelocity(new Vector()); + if (Events.fireAndTestCancel(new SpawnEntityEvent(event, create(block, entity), item))) { + item.remove(); + } + } } } } @@ -331,23 +354,21 @@ public void onPlayerBucketFill(PlayerBucketFillEvent event) { @EventHandler public void onBlockFromTo(BlockFromToEvent event) { - if (ABSTRACT_FROM_TO_EVENTS) { - Block from = event.getBlock(); - Block to = event.getToBlock(); + Block from = event.getBlock(); + Block to = event.getToBlock(); - // Liquids pass this event when flowing to solid blocks - if (to.getType().isSolid() && Materials.isLiquid(from.getType())) { - return; - } - - Cause cause = create(from); - - if (from.getType() != Material.AIR) { - Events.fireToCancel(event, new BreakBlockEvent(event, cause, to)); - } - - Events.fireToCancel(event, new PlaceBlockEvent(event, cause, to.getLocation(), from.getType())); + // Liquids pass this event when flowing to solid blocks + if (to.getType().isSolid() && Materials.isLiquid(from.getType())) { + return; } + + Cause cause = create(from); + + if (from.getType() != Material.AIR) { + Events.fireToCancel(event, new BreakBlockEvent(event, cause, to)); + } + + Events.fireToCancel(event, new PlaceBlockEvent(event, cause, to.getLocation(), from.getType())); } //------------------------------------------------------------------------- diff --git a/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java b/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java index e62d35a4..6eb1f5bc 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java @@ -33,12 +33,17 @@ import com.sk89q.worldguard.bukkit.event.entity.UseEntityEvent; import com.sk89q.worldguard.bukkit.util.Entities; import com.sk89q.worldguard.bukkit.util.Materials; +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.association.Associables; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.association.RegionOverlapAssociation; import com.sk89q.worldguard.protection.flags.DefaultFlag; import com.sk89q.worldguard.protection.flags.StateFlag; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.command.CommandSender; +import org.bukkit.block.Block; import org.bukkit.entity.Creeper; import org.bukkit.entity.EnderDragon; import org.bukkit.entity.Entity; @@ -65,11 +70,15 @@ public RegionProtectionListener(WorldGuardPlugin plugin) { /** * Tell a sender that s/he cannot do something 'here'. * - * @param sender the sender + * @param cause the cause * @param subject the subject that the sender was blocked from touching */ - private void tellErrorMessage(CommandSender sender, Object subject) { - sender.sendMessage(ChatColor.DARK_RED + "Sorry, but you are not allowed to do that here."); + private void tellErrorMessage(Cause cause, Object subject) { + Object rootCause = cause.getRootCause(); + + if (rootCause instanceof Player) { + ((Player) rootCause).sendMessage(ChatColor.DARK_RED + "Sorry, but you are not allowed to do that here."); + } } /** @@ -98,6 +107,24 @@ public boolean apply(@Nullable Location location) { }; } + private RegionAssociable createRegionAssociable(Cause cause) { + Object rootCause = cause.getRootCause(); + + if (rootCause instanceof Player) { + return getPlugin().wrapPlayer((Player) rootCause); + } else if (rootCause instanceof Entity) { + RegionQuery query = getPlugin().getRegionContainer().createQuery(); + ApplicableRegionSet source = query.getApplicableRegions(((Entity) rootCause).getLocation()); + return new RegionOverlapAssociation(source.getRegions()); + } else if (rootCause instanceof Block) { + RegionQuery query = getPlugin().getRegionContainer().createQuery(); + ApplicableRegionSet source = query.getApplicableRegions(((Block) rootCause).getLocation()); + return new RegionOverlapAssociation(source.getRegions()); + } else { + return Associables.constant(Association.NON_MEMBER); + } + } + @EventHandler(ignoreCancelled = true) public void onPlaceBlock(final PlaceBlockEvent event) { if (isWhitelisted(event.getCause())) { @@ -105,36 +132,30 @@ public void onPlaceBlock(final PlaceBlockEvent event) { } final Material type = event.getEffectiveMaterial(); + final RegionQuery query = getPlugin().getRegionContainer().createQuery(); + final RegionAssociable associable = createRegionAssociable(event.getCause()); - final Player player; + event.filterBlocks(new Predicate() { + @Override + public boolean apply(Location target) { + boolean canPlace; - if ((player = event.getCause().getPlayerRootCause()) != null) { - final RegionQuery query = getPlugin().getRegionContainer().createQuery(); + // Flint and steel, fire charge + if (type == Material.FIRE) { + canPlace = query.testBuild(target, associable, DefaultFlag.LIGHTER); - event.filterBlocks(new Predicate() { - @Override - public boolean apply(Location target) { - boolean canPlace; - - // Flint and steel, fire charge - if (type == Material.FIRE) { - canPlace = query.testBuild(target, player, DefaultFlag.LIGHTER); - - } else { - canPlace = query.testBuild(target, player); - } - - if (!canPlace) { - tellErrorMessage(player, target); - return false; - } - - return true; + } else { + canPlace = query.testBuild(target, associable); } - }); - } else { - event.setCancelled(true); - } + + if (!canPlace) { + tellErrorMessage(event.getCause(), target); + return false; + } + + return true; + } + }); } @EventHandler(ignoreCancelled = true) @@ -143,37 +164,13 @@ public void onBreakBlock(final BreakBlockEvent event) { return; // Whitelisted cause } - final RegionQuery query = getPlugin().getRegionContainer().createQuery(); ConfigurationManager globalConfig = getPlugin().getGlobalStateManager(); WorldConfiguration config = globalConfig.get(event.getWorld()); + final RegionQuery query = getPlugin().getRegionContainer().createQuery(); - final Player player; - final Entity entity; - - // ==================================================================== - // Player caused - // ==================================================================== - - if ((player = event.getCause().getPlayerRootCause()) != null) { - event.filterBlocks(new Predicate() { - @Override - public boolean apply(Location target) { - boolean canBreak = query.testBuild(target, player); - - if (!canBreak) { - tellErrorMessage(player, target); - return false; - } - - return true; - } - }); - - // ==================================================================== - // Entity caused - // ==================================================================== - - } else if ((entity = event.getCause().getEntityRootCause()) != null) { + // TODO: Move this to another event handler + Entity entity; + if ((entity = event.getCause().getEntityRootCause()) != null) { // Creeper if (entity instanceof Creeper) { event.filterBlocks(createStateTest(query, DefaultFlag.CREEPER_EXPLOSION), config.explosionFlagCancellation); @@ -187,8 +184,24 @@ public boolean apply(Location target) { event.filterBlocks(createStateTest(query, DefaultFlag.TNT), config.explosionFlagCancellation); } - } else { - event.setCancelled(true); + } + + if (!event.isCancelled()) { + final RegionAssociable associable = createRegionAssociable(event.getCause()); + + event.filterBlocks(new Predicate() { + @Override + public boolean apply(Location target) { + boolean canBreak = query.testBuild(target, associable); + + if (!canBreak) { + tellErrorMessage(event.getCause(), target); + return false; + } + + return true; + } + }); } } @@ -199,45 +212,39 @@ public void onUseBlock(final UseBlockEvent event) { } final Material type = event.getEffectiveMaterial(); + final RegionQuery query = getPlugin().getRegionContainer().createQuery(); + final RegionAssociable associable = createRegionAssociable(event.getCause()); - final Player player; + event.filterBlocks(new Predicate() { + @Override + public boolean apply(Location target) { + boolean canUse; - if ((player = event.getCause().getPlayerRootCause()) != null) { - final RegionQuery query = getPlugin().getRegionContainer().createQuery(); + // Inventory blocks (CHEST_ACCESS) + if (Materials.isInventoryBlock(type)) { + canUse = query.testBuild(target, associable, DefaultFlag.USE, DefaultFlag.CHEST_ACCESS); - event.filterBlocks(new Predicate() { - @Override - public boolean apply(Location target) { - boolean canUse; + // Beds (SLEEP) + } else if (type == Material.BED) { + canUse = query.testBuild(target, associable, DefaultFlag.USE, DefaultFlag.SLEEP); - // Inventory blocks (CHEST_ACCESS) - if (Materials.isInventoryBlock(type)) { - canUse = query.testBuild(target, player, DefaultFlag.USE, DefaultFlag.CHEST_ACCESS); + // TNT (TNT) + } else if (type == Material.TNT) { + canUse = query.testBuild(target, associable, DefaultFlag.TNT); - // Beds (SLEEP) - } else if (type == Material.BED) { - canUse = query.testBuild(target, player, DefaultFlag.USE, DefaultFlag.SLEEP); - - // TNT (TNT) - } else if (type == Material.TNT) { - canUse = query.testBuild(target, player, DefaultFlag.TNT); - - // Everything else - } else { - canUse = query.testBuild(target, player, DefaultFlag.USE); - } - - if (!canUse) { - tellErrorMessage(player, target); - return false; - } - - return true; + // Everything else + } else { + canUse = query.testBuild(target, associable, DefaultFlag.USE); } - }); - } else { - event.setCancelled(true); - } + + if (!canUse) { + tellErrorMessage(event.getCause(), target); + return false; + } + + return true; + } + }); } @EventHandler(ignoreCancelled = true) @@ -249,23 +256,19 @@ public void onSpawnEntity(SpawnEntityEvent event) { Location target = event.getTarget(); EntityType type = event.getEffectiveType(); - Player player; + RegionQuery query = getPlugin().getRegionContainer().createQuery(); + RegionAssociable associable = createRegionAssociable(event.getCause()); - if ((player = event.getCause().getPlayerRootCause()) != null) { - RegionQuery query = getPlugin().getRegionContainer().createQuery(); - boolean canSpawn; + boolean canSpawn; - if (Entities.isVehicle(type)) { - canSpawn = query.testBuild(target, player, DefaultFlag.PLACE_VEHICLE); - } else { - canSpawn = query.testBuild(target, player); - } - - if (!canSpawn) { - tellErrorMessage(player, target); - event.setCancelled(true); - } + if (Entities.isVehicle(type)) { + canSpawn = query.testBuild(target, associable, DefaultFlag.PLACE_VEHICLE); } else { + canSpawn = query.testBuild(target, associable); + } + + if (!canSpawn) { + tellErrorMessage(event.getCause(), target); event.setCancelled(true); } } @@ -278,24 +281,19 @@ public void onDestroyEntity(DestroyEntityEvent event) { Location target = event.getTarget(); EntityType type = event.getEntity().getType(); + RegionAssociable associable = createRegionAssociable(event.getCause()); - Player player; + RegionQuery query = getPlugin().getRegionContainer().createQuery(); + boolean canDestroy; - if ((player = event.getCause().getPlayerRootCause()) != null) { - RegionQuery query = getPlugin().getRegionContainer().createQuery(); - boolean canDestroy; - - if (Entities.isVehicle(type)) { - canDestroy = query.testBuild(target, player, DefaultFlag.DESTROY_VEHICLE); - } else { - canDestroy = query.testBuild(target, player); - } - - if (!canDestroy) { - tellErrorMessage(player, target); - event.setCancelled(true); - } + if (Entities.isVehicle(type)) { + canDestroy = query.testBuild(target, associable, DefaultFlag.DESTROY_VEHICLE); } else { + canDestroy = query.testBuild(target, associable); + } + + if (!canDestroy) { + tellErrorMessage(event.getCause(), target); event.setCancelled(true); } } @@ -307,18 +305,13 @@ public void onUseEntity(UseEntityEvent event) { } Location target = event.getTarget(); + RegionAssociable associable = createRegionAssociable(event.getCause()); - Player player; + RegionQuery query = getPlugin().getRegionContainer().createQuery(); + boolean canUse = query.testBuild(target, associable, DefaultFlag.USE); - if ((player = event.getCause().getPlayerRootCause()) != null) { - RegionQuery query = getPlugin().getRegionContainer().createQuery(); - boolean canUse = query.testBuild(target, player, DefaultFlag.USE); - - if (!canUse) { - tellErrorMessage(player, target); - event.setCancelled(true); - } - } else { + if (!canUse) { + tellErrorMessage(event.getCause(), target); event.setCancelled(true); } } diff --git a/src/main/java/com/sk89q/worldguard/bukkit/util/BlockStateAsBlockFunction.java b/src/main/java/com/sk89q/worldguard/bukkit/util/BlockStateAsBlockFunction.java new file mode 100644 index 00000000..26b42def --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/bukkit/util/BlockStateAsBlockFunction.java @@ -0,0 +1,35 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldguard.bukkit.util; + +import com.google.common.base.Function; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; + +import javax.annotation.Nullable; + +public class BlockStateAsBlockFunction implements Function { + + @Override + public Block apply(@Nullable BlockState blockState) { + return blockState != null ? blockState.getBlock() : null; + } + +} diff --git a/src/main/java/com/sk89q/worldguard/domains/Association.java b/src/main/java/com/sk89q/worldguard/domains/Association.java new file mode 100644 index 00000000..d15b60e3 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/domains/Association.java @@ -0,0 +1,31 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldguard.domains; + +/** + * Indicates the level of membership. + */ +public enum Association { + + OWNER, + MEMBER, + NON_MEMBER + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java b/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java index 1ec49a7e..f25c0128 100644 --- a/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java +++ b/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java @@ -21,6 +21,7 @@ import com.google.common.collect.ObjectArrays; import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.association.RegionAssociable; import com.sk89q.worldguard.protection.flags.DefaultFlag; import com.sk89q.worldguard.protection.flags.Flag; import com.sk89q.worldguard.protection.flags.RegionGroup; @@ -34,6 +35,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @@ -88,11 +90,11 @@ public ApplicableRegionSet(SortedSet applicable, @Nullable Prot *

If there are several relevant flags (i.e. in addition to * {@code BUILD}, such as {@link DefaultFlag#SLEEP} when the target * object is a bed), then - * {@link #testBuild(LocalPlayer, StateFlag...)} should be used.

+ * {@link #testBuild(RegionAssociable, StateFlag...)} should be used.

* * @param player the player to check * @return true if permitted - * @deprecated use {@link #testBuild(LocalPlayer, StateFlag...)} + * @deprecated use {@link #testBuild(RegionAssociable, StateFlag...)} */ @Deprecated public boolean canBuild(LocalPlayer player) { @@ -105,36 +107,36 @@ public boolean canBuild(LocalPlayer player) { * considering the {@link DefaultFlag#BUILD} flag. * *

This method is equivalent to calling - * {@link #testState(LocalPlayer, StateFlag...)} with {@code flags} plus + * {@link #testState(RegionAssociable, StateFlag...)} with {@code flags} plus * the {@code BUILD} flag.

* - * @param player the player + * @param subject the subject * @param flags zero or more flags * @return true if permission is granted - * @see #queryState(LocalPlayer, StateFlag...) + * @see #queryState(RegionAssociable, StateFlag...) */ - public boolean testBuild(LocalPlayer player, StateFlag... flags) { - checkNotNull(player); - return test(flagValueCalculator.queryState(player, ObjectArrays.concat(flags, DefaultFlag.BUILD))); + public boolean testBuild(RegionAssociable subject, StateFlag... flags) { + checkNotNull(subject); + return test(flagValueCalculator.queryState(subject, ObjectArrays.concat(flags, DefaultFlag.BUILD))); } /** * Test whether the (effective) value for a list of state flags equals * {@code ALLOW}. * - *

{@code player} can be non-null to satisfy region group requirements, + *

{@code subject} can be non-null to satisfy region group requirements, * otherwise it will be assumed that the caller that is not a member of any * regions. (Flags on a region can be changed so that they only apply - * to certain users.) The player argument is required if the + * to certain users.) The subject argument is required if the * {@link DefaultFlag#BUILD} flag is in the list of flags.

* - * @param player an optional player, which would be used to determine the region groups that apply + * @param subject an optional subject, which would be used to determine the region groups that apply * @param flags a list of flags to check * @return true if the result was {@code ALLOW} - * @see #queryState(LocalPlayer, StateFlag...) + * @see #queryState(RegionAssociable, StateFlag...) */ - public boolean testState(@Nullable LocalPlayer player, StateFlag... flags) { - return test(flagValueCalculator.queryState(player, flags)); + public boolean testState(@Nullable RegionAssociable subject, StateFlag... flags) { + return test(flagValueCalculator.queryState(subject, flags)); } /** @@ -142,19 +144,19 @@ public boolean testState(@Nullable LocalPlayer player, StateFlag... flags) { * states is observed here; that is, {@code DENY} overrides {@code ALLOW}, * and {@code ALLOW} overrides {@code NONE}. One flag may override another. * - *

{@code player} can be non-null to satisfy region group requirements, + *

{@code subject} can be non-null to satisfy region group requirements, * otherwise it will be assumed that the caller that is not a member of any * regions. (Flags on a region can be changed so that they only apply - * to certain users.) The player argument is required if the + * to certain users.) The subject argument is required if the * {@link DefaultFlag#BUILD} flag is in the list of flags.

* - * @param player an optional player, which would be used to determine the region groups that apply + * @param subject an optional subject, which would be used to determine the region groups that apply * @param flags a list of flags to check * @return a state */ @Nullable - public State queryState(@Nullable LocalPlayer player, StateFlag... flags) { - return flagValueCalculator.queryState(player, flags); + public State queryState(@Nullable RegionAssociable subject, StateFlag... flags) { + return flagValueCalculator.queryState(subject, flags); } /** @@ -169,19 +171,19 @@ public State queryState(@Nullable LocalPlayer player, StateFlag... flags) { * type of flag that actually has a strategy for picking a value is the * {@link StateFlag}.

* - *

{@code player} can be non-null to satisfy region group requirements, + *

{@code subject} can be non-null to satisfy region group requirements, * otherwise it will be assumed that the caller that is not a member of any * regions. (Flags on a region can be changed so that they only apply - * to certain users.) The player argument is required if the + * to certain users.) The subject argument is required if the * {@link DefaultFlag#BUILD} flag is the flag being queried.

* - * @param player an optional player, which would be used to determine the region group to apply + * @param subject an optional subject, which would be used to determine the region group to apply * @param flag the flag * @return a value, which could be {@code null} */ @Nullable - public V queryValue(@Nullable LocalPlayer player, Flag flag) { - return flagValueCalculator.queryValue(player, flag); + public V queryValue(@Nullable RegionAssociable subject, Flag flag) { + return flagValueCalculator.queryValue(subject, flag); } /** @@ -189,18 +191,18 @@ public V queryValue(@Nullable LocalPlayer player, Flag flag) { * values. It is up to the caller to determine which value, if any, * from the collection will be used. * - *

{@code player} can be non-null to satisfy region group requirements, + *

{@code subject} can be non-null to satisfy region group requirements, * otherwise it will be assumed that the caller that is not a member of any * regions. (Flags on a region can be changed so that they only apply - * to certain users.) The player argument is required if the + * to certain users.) The subject argument is required if the * {@link DefaultFlag#BUILD} flag is the flag being queried.

* - * @param player an optional player, which would be used to determine the region group to apply + * @param subject an optional subject, which would be used to determine the region group to apply * @param flag the flag * @return a collection of values */ - public Collection queryAllValues(@Nullable LocalPlayer player, Flag flag) { - return flagValueCalculator.queryAllValues(player, flag); + public Collection queryAllValues(@Nullable RegionAssociable subject, Flag flag) { + return flagValueCalculator.queryAllValues(subject, flag); } /** @@ -224,7 +226,7 @@ public boolean canConstruct(LocalPlayer player) { * @param flag flag to check * @return whether it is allowed * @throws IllegalArgumentException if the build flag is given - * @deprecated use {@link #queryState(LocalPlayer, StateFlag...)} instead + * @deprecated use {@link #queryState(RegionAssociable, StateFlag...)} instead */ @Deprecated public boolean allows(StateFlag flag) { @@ -244,7 +246,7 @@ public boolean allows(StateFlag flag) { * @param player player (used by some flags) * @return whether the state is allows for it * @throws IllegalArgumentException if the build flag is given - * @deprecated use {@link #queryState(LocalPlayer, StateFlag...)} instead + * @deprecated use {@link #queryState(RegionAssociable, StateFlag...)} instead */ @Deprecated public boolean allows(StateFlag flag, @Nullable LocalPlayer player) { @@ -299,7 +301,7 @@ public boolean isMemberOfAll(LocalPlayer player) { * * @param flag the flag to check * @return value of the flag, which may be null - * @deprecated Use {@link #queryValue(LocalPlayer, Flag)} instead. There + * @deprecated Use {@link #queryValue(RegionAssociable, Flag)} instead. There * is no difference in functionality. */ @Deprecated @@ -316,7 +318,7 @@ public , V> V getFlag(T flag) { * @param groupPlayer player to check {@link RegionGroup}s against * @return value of the flag, which may be null * @throws IllegalArgumentException if a StateFlag is given - * @deprecated Use {@link #queryValue(LocalPlayer, Flag)} instead. There + * @deprecated Use {@link #queryValue(RegionAssociable, Flag)} instead. There * is no difference in functionality. */ @Deprecated @@ -334,6 +336,15 @@ public int size() { return applicable.size(); } + /** + * Get an immutable set of regions that are included in this set. + * + * @return a set of regions + */ + public Set getRegions() { + return Collections.unmodifiableSet(applicable); + } + @Override public Iterator iterator() { return applicable.iterator(); diff --git a/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java b/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java index 6b2e1a49..7918c884 100644 --- a/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java +++ b/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java @@ -21,11 +21,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; -import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.association.RegionAssociable; import com.sk89q.worldguard.protection.flags.DefaultFlag; import com.sk89q.worldguard.protection.flags.Flag; import com.sk89q.worldguard.protection.flags.RegionGroup; -import com.sk89q.worldguard.protection.flags.RegionGroupFlag; import com.sk89q.worldguard.protection.flags.StateFlag; import com.sk89q.worldguard.protection.flags.StateFlag.State; import com.sk89q.worldguard.protection.regions.ProtectedRegion; @@ -84,9 +84,9 @@ private Iterable getApplicable() { } /** - * Return the membership status of the given player, indicating + * Return the membership status of the given subject, indicating * whether there are no (counted) regions in the list of regions, - * whether the player is a member of all regions, or whether + * whether the subject is a member of all regions, or whether * the region is not a member of all regions. * *

A region is "counted" if it doesn't have the @@ -97,20 +97,20 @@ private Iterable getApplicable() { *

This method is mostly for internal use. It's not particularly * useful.

* - * @param player the player + * @param subject the subject * @return the membership result */ - public Result getMembership(LocalPlayer player) { - checkNotNull(player); + public Result getMembership(RegionAssociable subject) { + checkNotNull(subject); int minimumPriority = Integer.MIN_VALUE; boolean foundApplicableRegion = false; // Say there are two regions in one location: CHILD and PARENT (CHILD // is a child of PARENT). If there are two overlapping regions in WG, a - // player has to be a member of /both/ (or flags permit) in order to + // subject has to be a member of /both/ (or flags permit) in order to // build in that location. However, inheritance is supposed - // to allow building if the player is a member of just CHILD. That + // to allow building if the subject is a member of just CHILD. That // presents a problem. // // To rectify this, we keep two sets. When we iterate over the list of @@ -145,7 +145,7 @@ public Result getMembership(LocalPlayer player) { } // If PASSTHROUGH is set, ignore this region - if (getEffectiveFlag(region, DefaultFlag.PASSTHROUGH, player) == State.ALLOW) { + if (getEffectiveFlag(region, DefaultFlag.PASSTHROUGH, subject) == State.ALLOW) { continue; } @@ -153,7 +153,7 @@ public Result getMembership(LocalPlayer player) { foundApplicableRegion = true; if (!hasCleared.contains(region)) { - if (!region.isMember(player)) { + if (!RegionGroup.MEMBERS.contains(subject.getAssociation(region))) { needsClear.add(region); } else { // Need to clear all parents @@ -175,24 +175,24 @@ public Result getMembership(LocalPlayer player) { * states is observed here; that is, {@code DENY} overrides {@code ALLOW}, * and {@code ALLOW} overrides {@code NONE}. * - *

A player can be provided that is used to determine whether the value + *

A subject can be provided that is used to determine whether the value * of a flag on a particular region should be used. For example, if a * flag's region group is set to {@link RegionGroup#MEMBERS} and the given - * player is not a member, then the region would be skipped when - * querying that flag. If {@code null} is provided for the player, then + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then * only flags that use {@link RegionGroup#ALL}, * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

* - * @param player an optional player, which would be used to determine the region group to apply + * @param subject an optional subject, which would be used to determine the region group to apply * @param flags a list of flags to check * @return a state */ @Nullable - public State queryState(@Nullable LocalPlayer player, StateFlag... flags) { + public State queryState(@Nullable RegionAssociable subject, StateFlag... flags) { State value = null; for (StateFlag flag : flags) { - value = StateFlag.combine(value, queryValue(player, flag)); + value = StateFlag.combine(value, queryValue(subject, flag)); if (value == State.DENY) { break; } @@ -214,21 +214,21 @@ public State queryState(@Nullable LocalPlayer player, StateFlag... flags) { * type of flag that can consistently return the same 'best' value is * {@link StateFlag}.

* - *

A player can be provided that is used to determine whether the value + *

A subject can be provided that is used to determine whether the value * of a flag on a particular region should be used. For example, if a * flag's region group is set to {@link RegionGroup#MEMBERS} and the given - * player is not a member, then the region would be skipped when - * querying that flag. If {@code null} is provided for the player, then + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then * only flags that use {@link RegionGroup#ALL}, * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

* - * @param player an optional player, which would be used to determine the region group to apply + * @param subject an optional subject, which would be used to determine the region group to apply * @param flag the flag * @return a value, which could be {@code null} */ @Nullable - public V queryValue(@Nullable LocalPlayer player, Flag flag) { - Collection values = queryAllValues(player, flag); + public V queryValue(@Nullable RegionAssociable subject, Flag flag) { + Collection values = queryAllValues(subject, flag); return flag.chooseValue(values); } @@ -237,20 +237,20 @@ public V queryValue(@Nullable LocalPlayer player, Flag flag) { * values. It is up to the caller to determine which value, if any, * from the collection will be used. * - *

A player can be provided that is used to determine whether the value + *

A subject can be provided that is used to determine whether the value * of a flag on a particular region should be used. For example, if a * flag's region group is set to {@link RegionGroup#MEMBERS} and the given - * player is not a member, then the region would be skipped when - * querying that flag. If {@code null} is provided for the player, then + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then * only flags that use {@link RegionGroup#ALL}, * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

* - * @param player an optional player, which would be used to determine the region group to apply + * @param subject an optional subject, which would be used to determine the region group to apply * @param flag the flag * @return a collection of values */ @SuppressWarnings("unchecked") - public Collection queryAllValues(@Nullable LocalPlayer player, Flag flag) { + public Collection queryAllValues(@Nullable RegionAssociable subject, Flag flag) { checkNotNull(flag); int minimumPriority = Integer.MIN_VALUE; @@ -295,7 +295,7 @@ public Collection queryAllValues(@Nullable LocalPlayer player, Flag fl break; } - V value = getEffectiveFlag(region, flag, player); + V value = getEffectiveFlag(region, flag, subject); if (value != null) { if (!ignoredRegions.contains(region)) { @@ -314,11 +314,11 @@ public Collection queryAllValues(@Nullable LocalPlayer player, Flag fl } if (flag == DefaultFlag.BUILD && consideredValues.isEmpty()) { - if (player == null) { - throw new NullPointerException("The BUILD flag is handled in a special fashion and requires a non-null player parameter"); + if (subject == null) { + throw new NullPointerException("The BUILD flag is handled in a special fashion and requires a non-null subject parameter"); } - switch (getMembership(player)) { + switch (getMembership(subject)) { case FAIL: return ImmutableList.of(); case SUCCESS: @@ -359,10 +359,11 @@ public int getPriority(final ProtectedRegion region) { * * @param region the region * @param flag the flag + * @param subject an subject object * @return the value */ @SuppressWarnings("unchecked") - public V getEffectiveFlag(final ProtectedRegion region, Flag flag, @Nullable LocalPlayer player) { + public V getEffectiveFlag(final ProtectedRegion region, Flag flag, @Nullable RegionAssociable subject) { if (region == globalRegion) { if (flag == DefaultFlag.PASSTHROUGH) { // Has members/owners -> the global region acts like @@ -393,7 +394,9 @@ public V getEffectiveFlag(final ProtectedRegion region, Flag flag, @Nulla group = flag.getRegionGroupFlag().getDefault(); } - if (!RegionGroupFlag.isMember(region, group, player)) { + if (subject == null) { + use = group.contains(Association.NON_MEMBER); + } else if (!group.contains(subject.getAssociation(region))) { use = false; } } @@ -448,7 +451,7 @@ private void ignoreValuesOfParents(Map needsClear, Set + * 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 . + */ + +package com.sk89q.worldguard.protection.association; + +import com.sk89q.worldguard.domains.Association; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Utility methods to deal with associables. + */ +public final class Associables { + + private static final RegionAssociable OWNER_ASSOCIABLE = new ConstantAssociation(Association.OWNER); + private static final RegionAssociable MEMBER_ASSOCIABLE = new ConstantAssociation(Association.MEMBER); + private static final RegionAssociable NON_MEMBER_ASSOCIABLE = new ConstantAssociation(Association.NON_MEMBER); + + private Associables() { + } + + /** + * Get an instance that always returns the same association. + * + * @param association the association + * @return the instance + */ + public static RegionAssociable constant(Association association) { + checkNotNull(association); + switch (association) { + case OWNER: + return OWNER_ASSOCIABLE; + case MEMBER: + return MEMBER_ASSOCIABLE; + case NON_MEMBER: + return NON_MEMBER_ASSOCIABLE; + default: + return new ConstantAssociation(association); + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/association/ConstantAssociation.java b/src/main/java/com/sk89q/worldguard/protection/association/ConstantAssociation.java new file mode 100644 index 00000000..95e8d5e9 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/association/ConstantAssociation.java @@ -0,0 +1,38 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldguard.protection.association; + +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +class ConstantAssociation implements RegionAssociable { + + private final Association association; + + ConstantAssociation(Association association) { + this.association = association; + } + + @Override + public Association getAssociation(ProtectedRegion region) { + return association; + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/association/RegionAssociable.java b/src/main/java/com/sk89q/worldguard/protection/association/RegionAssociable.java new file mode 100644 index 00000000..07e8c3f4 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/association/RegionAssociable.java @@ -0,0 +1,38 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldguard.protection.association; + +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +/** + * An object that can have membership in a region. + */ +public interface RegionAssociable { + + /** + * Get the most specific association level for the input region. + * + * @param region the input region + * @return the most specific membership level + */ + Association getAssociation(ProtectedRegion region); + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/association/RegionOverlapAssociation.java b/src/main/java/com/sk89q/worldguard/protection/association/RegionOverlapAssociation.java new file mode 100644 index 00000000..7165c9c2 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/association/RegionOverlapAssociation.java @@ -0,0 +1,56 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldguard.protection.association; + +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Determines that the association to a region is {@code OWNER} if the input + * region is in a set of source regions. + */ +public class RegionOverlapAssociation implements RegionAssociable { + + private final Set source; + + /** + * Create a new instance. + * + * @param source set of regions that input regions must be contained within + */ + public RegionOverlapAssociation(Set source) { + checkNotNull(source); + this.source = source; + } + + @Override + public Association getAssociation(ProtectedRegion region) { + if (source.contains(region)) { + return Association.OWNER; + } else { + return Association.NON_MEMBER; + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java b/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java index 0d7f6e55..b1f1598f 100644 --- a/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java +++ b/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java @@ -19,14 +19,40 @@ package com.sk89q.worldguard.protection.flags; +import com.google.common.collect.ImmutableSet; +import com.sk89q.worldguard.domains.Association; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + /** - * A grouping of region membership types + * A grouping of region membership types. */ public enum RegionGroup { - MEMBERS, - OWNERS, - NON_MEMBERS, - NON_OWNERS, - ALL, - NONE + + MEMBERS(Association.MEMBER, Association.OWNER), + OWNERS(Association.OWNER), + NON_MEMBERS(Association.NON_MEMBER), + NON_OWNERS(Association.MEMBER, Association.NON_MEMBER), + ALL(Association.OWNER, Association.MEMBER, Association.NON_MEMBER), + NONE(); + + private final Set contained; + + RegionGroup(Association... association) { + this.contained = ImmutableSet.copyOf(association); + } + + /** + * Test whether this group contains the given membership status. + * + * @param association membership status + * @return true if contained + */ + public boolean contains(Association association) { + checkNotNull(association); + return contained.contains(association); + } + }