Fix trailing whitespace and correctly check both permisions systems in WorldGurdPlugin.broadcastNotification()

This commit is contained in:
zml2008 2012-03-10 17:56:02 -08:00
parent 3d6d774b17
commit 5454832918

View File

@ -28,6 +28,7 @@
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@ -43,6 +44,7 @@
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.permissions.Permissible;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -56,11 +58,11 @@
/** /**
* The main class for WorldGuard as a Bukkit plugin. * The main class for WorldGuard as a Bukkit plugin.
* *
* @author sk89q * @author sk89q
*/ */
public class WorldGuardPlugin extends JavaPlugin { public class WorldGuardPlugin extends JavaPlugin {
/** /**
* Manager for commands. This automatically handles nested commands, * Manager for commands. This automatically handles nested commands,
* permissions checking, and a number of other fancy command things. * permissions checking, and a number of other fancy command things.
@ -72,12 +74,12 @@ public class WorldGuardPlugin extends JavaPlugin {
* Handles the region databases for all worlds. * Handles the region databases for all worlds.
*/ */
private final GlobalRegionManager globalRegionManager; private final GlobalRegionManager globalRegionManager;
/** /**
* Handles all configuration. * Handles all configuration.
*/ */
private final ConfigurationManager configuration; private final ConfigurationManager configuration;
/** /**
* Used for scheduling flags. * Used for scheduling flags.
*/ */
@ -99,7 +101,7 @@ public boolean hasPermission(CommandSender player, String perm) {
} }
}; };
} }
/** /**
* Called on plugin enable. * Called on plugin enable.
*/ */
@ -113,7 +115,7 @@ public void onEnable() {
final CommandsManagerRegistration reg = new CommandsManagerRegistration(this, commands); final CommandsManagerRegistration reg = new CommandsManagerRegistration(this, commands);
reg.register(ToggleCommands.class); reg.register(ToggleCommands.class);
reg.register(ProtectionCommands.class); reg.register(ProtectionCommands.class);
getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() { getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {
@Override @Override
public void run() { public void run() {
@ -130,11 +132,11 @@ public void run() {
// This must be done before configuration is loaded // This must be done before configuration is loaded
LegacyWorldGuardMigration.migrateBlacklist(this); LegacyWorldGuardMigration.migrateBlacklist(this);
// Load the configuration // Load the configuration
configuration.load(); configuration.load();
globalRegionManager.preload(); globalRegionManager.preload();
// Migrate regions after the regions were loaded because // Migrate regions after the regions were loaded because
// the migration code reuses the loaded region managers // the migration code reuses the loaded region managers
LegacyWorldGuardMigration.migrateRegions(this); LegacyWorldGuardMigration.migrateRegions(this);
@ -152,7 +154,7 @@ public void run() {
(new WorldGuardWeatherListener(this)).registerEvents(); (new WorldGuardWeatherListener(this)).registerEvents();
(new WorldGuardVehicleListener(this)).registerEvents(); (new WorldGuardVehicleListener(this)).registerEvents();
(new WorldGuardServerListener(this)).registerEvents(); (new WorldGuardServerListener(this)).registerEvents();
if (getServer().getPluginManager().isPluginEnabled("CommandBook")) { if (getServer().getPluginManager().isPluginEnabled("CommandBook")) {
getServer().getPluginManager().registerEvents(new WorldGuardCommandBookListener(this), this); getServer().getPluginManager().registerEvents(new WorldGuardCommandBookListener(this), this);
} }
@ -183,7 +185,7 @@ public void onDisable() {
configuration.unload(); configuration.unload();
this.getServer().getScheduler().cancelTasks(this); this.getServer().getScheduler().cancelTasks(this);
} }
/** /**
* Handle a command. * Handle a command.
*/ */
@ -209,13 +211,13 @@ public boolean onCommand(CommandSender sender, Command cmd, String label,
} catch (CommandException e) { } catch (CommandException e) {
sender.sendMessage(ChatColor.RED + e.getMessage()); sender.sendMessage(ChatColor.RED + e.getMessage());
} }
return true; return true;
} }
/** /**
* Get the GlobalRegionManager. * Get the GlobalRegionManager.
* *
* @return The plugin's global region manager * @return The plugin's global region manager
*/ */
public GlobalRegionManager getGlobalRegionManager() { public GlobalRegionManager getGlobalRegionManager() {
@ -232,10 +234,10 @@ public GlobalRegionManager getGlobalRegionManager() {
public ConfigurationManager getGlobalConfiguration() { public ConfigurationManager getGlobalConfiguration() {
return getGlobalStateManager(); return getGlobalStateManager();
} }
/** /**
* Gets the flag state manager. * Gets the flag state manager.
* *
* @return The flag state manager * @return The flag state manager
*/ */
public FlagStateManager getFlagStateManager() { public FlagStateManager getFlagStateManager() {
@ -252,9 +254,9 @@ public ConfigurationManager getGlobalStateManager() {
} }
/** /**
* Check whether a player is in a group. * Check whether a player is in a group.
* This calls the corresponding method in PermissionsResolverManager * This calls the corresponding method in PermissionsResolverManager
* *
* @param player The player to check * @param player The player to check
* @param group The group * @param group The group
* @return whether {@code player} is in {@code group} * @return whether {@code player} is in {@code group}
@ -282,11 +284,11 @@ public String[] getGroups(Player player) {
return new String[0]; return new String[0];
} }
} }
/** /**
* Gets the name of a command sender. This is a unique name and this * Gets the name of a command sender. This is a unique name and this
* method should never return a "display name". * method should never return a "display name".
* *
* @param sender The sender to get the name of * @param sender The sender to get the name of
* @return The unique name of the sender. * @return The unique name of the sender.
*/ */
@ -297,10 +299,10 @@ public String toUniqueName(CommandSender sender) {
return sender.getName(); return sender.getName();
} }
} }
/** /**
* Gets the name of a command sender. This play be a display name. * Gets the name of a command sender. This play be a display name.
* *
* @param sender The CommandSender to get the name of. * @param sender The CommandSender to get the name of.
* @return The name of the given sender * @return The name of the given sender
*/ */
@ -313,10 +315,10 @@ public String toName(CommandSender sender) {
return sender.getName(); return sender.getName();
} }
} }
/** /**
* Checks permissions. * Checks permissions.
* *
* @param sender The sender to check the permission on. * @param sender The sender to check the permission on.
* @param perm The permission to check the permission on. * @param perm The permission to check the permission on.
* @return whether {@code sender} has {@code perm} * @return whether {@code sender} has {@code perm}
@ -332,19 +334,19 @@ public boolean hasPermission(CommandSender sender, String perm) {
return true; return true;
} }
} }
// Invoke the permissions resolver // Invoke the permissions resolver
if (sender instanceof Player) { if (sender instanceof Player) {
Player player = (Player) sender; Player player = (Player) sender;
return PermissionsResolverManager.getInstance().hasPermission(player.getWorld().getName(), player.getName(), perm); return PermissionsResolverManager.getInstance().hasPermission(player.getWorld().getName(), player.getName(), perm);
} }
return false; return false;
} }
/** /**
* Checks permissions and throws an exception if permission is not met. * Checks permissions and throws an exception if permission is not met.
* *
* @param sender The sender to check the permission on. * @param sender The sender to check the permission on.
* @param perm The permission to check the permission on. * @param perm The permission to check the permission on.
* @throws CommandPermissionsException if {@code sender} doesn't have {@code perm} * @throws CommandPermissionsException if {@code sender} doesn't have {@code perm}
@ -355,10 +357,10 @@ public void checkPermission(CommandSender sender, String perm)
throw new CommandPermissionsException(); throw new CommandPermissionsException();
} }
} }
/** /**
* Checks to see if the sender is a player, otherwise throw an exception. * Checks to see if the sender is a player, otherwise throw an exception.
* *
* @param sender The {@link CommandSender} to check * @param sender The {@link CommandSender} to check
* @return {@code sender} casted to a player * @return {@code sender} casted to a player
* @throws CommandException if {@code sender} isn't a {@link Player} * @throws CommandException if {@code sender} isn't a {@link Player}
@ -371,15 +373,15 @@ public Player checkPlayer(CommandSender sender)
throw new CommandException("A player is expected."); throw new CommandException("A player is expected.");
} }
} }
/** /**
* Match player names. * Match player names.
* *
* The filter string uses the following format: * The filter string uses the following format:
* @[name] looks up all players with the exact {@code name} * @[name] looks up all players with the exact {@code name}
* *[name] matches any player whose name contains {@code name} * *[name] matches any player whose name contains {@code name}
* [name] matches any player whose name starts with {@code name} * [name] matches any player whose name starts with {@code name}
* *
* @param filter The filter string to check. * @param filter The filter string to check.
* @return A {@link List} of players who match {@code filter} * @return A {@link List} of players who match {@code filter}
*/ */
@ -387,11 +389,11 @@ public List<Player> matchPlayerNames(String filter) {
Player[] players = getServer().getOnlinePlayers(); Player[] players = getServer().getOnlinePlayers();
filter = filter.toLowerCase(); filter = filter.toLowerCase();
// Allow exact name matching // Allow exact name matching
if (filter.charAt(0) == '@' && filter.length() >= 2) { if (filter.charAt(0) == '@' && filter.length() >= 2) {
filter = filter.substring(1); filter = filter.substring(1);
for (Player player : players) { for (Player player : players) {
if (player.getName().equalsIgnoreCase(filter)) { if (player.getName().equalsIgnoreCase(filter)) {
List<Player> list = new ArrayList<Player>(); List<Player> list = new ArrayList<Player>();
@ -399,40 +401,40 @@ public List<Player> matchPlayerNames(String filter) {
return list; return list;
} }
} }
return new ArrayList<Player>(); return new ArrayList<Player>();
// Allow partial name matching // Allow partial name matching
} else if (filter.charAt(0) == '*' && filter.length() >= 2) { } else if (filter.charAt(0) == '*' && filter.length() >= 2) {
filter = filter.substring(1); filter = filter.substring(1);
List<Player> list = new ArrayList<Player>(); List<Player> list = new ArrayList<Player>();
for (Player player : players) { for (Player player : players) {
if (player.getName().toLowerCase().contains(filter)) { if (player.getName().toLowerCase().contains(filter)) {
list.add(player); list.add(player);
} }
} }
return list; return list;
// Start with name matching // Start with name matching
} else { } else {
List<Player> list = new ArrayList<Player>(); List<Player> list = new ArrayList<Player>();
for (Player player : players) { for (Player player : players) {
if (player.getName().toLowerCase().startsWith(filter)) { if (player.getName().toLowerCase().startsWith(filter)) {
list.add(player); list.add(player);
} }
} }
return list; return list;
} }
} }
/** /**
* Checks if the given list of players is greater than size 0, otherwise * Checks if the given list of players is greater than size 0, otherwise
* throw an exception. * throw an exception.
* *
* @param players The {@link List} to check * @param players The {@link List} to check
* @return {@code players} as an {@link Iterable} * @return {@code players} as an {@link Iterable}
* @throws CommandException If {@code players} is empty * @throws CommandException If {@code players} is empty
@ -443,10 +445,10 @@ protected Iterable<Player> checkPlayerMatch(List<Player> players)
if (players.size() == 0) { if (players.size() == 0) {
throw new CommandException("No players matched query."); throw new CommandException("No players matched query.");
} }
return players; return players;
} }
/** /**
* Matches players based on the specified filter string * Matches players based on the specified filter string
* *
@ -464,11 +466,11 @@ protected Iterable<Player> checkPlayerMatch(List<Player> players)
*/ */
public Iterable<Player> matchPlayers(CommandSender source, String filter) public Iterable<Player> matchPlayers(CommandSender source, String filter)
throws CommandException { throws CommandException {
if (getServer().getOnlinePlayers().length == 0) { if (getServer().getOnlinePlayers().length == 0) {
throw new CommandException("No players matched query."); throw new CommandException("No players matched query.");
} }
if (filter.equals("*")) { if (filter.equals("*")) {
return checkPlayerMatch(Arrays.asList(getServer().getOnlinePlayers())); return checkPlayerMatch(Arrays.asList(getServer().getOnlinePlayers()));
} }
@ -481,7 +483,7 @@ public Iterable<Player> matchPlayers(CommandSender source, String filter)
List<Player> players = new ArrayList<Player>(); List<Player> players = new ArrayList<Player>();
Player sourcePlayer = checkPlayer(source); Player sourcePlayer = checkPlayer(source);
World sourceWorld = sourcePlayer.getWorld(); World sourceWorld = sourcePlayer.getWorld();
for (Player player : getServer().getOnlinePlayers()) { for (Player player : getServer().getOnlinePlayers()) {
if (player.getWorld().equals(sourceWorld)) { if (player.getWorld().equals(sourceWorld)) {
players.add(player); players.add(player);
@ -489,7 +491,7 @@ public Iterable<Player> matchPlayers(CommandSender source, String filter)
} }
return checkPlayerMatch(players); return checkPlayerMatch(players);
// Handle #near, which is for nearby players. // Handle #near, which is for nearby players.
} else if (filter.equalsIgnoreCase("#near")) { } else if (filter.equalsIgnoreCase("#near")) {
List<Player> players = new ArrayList<Player>(); List<Player> players = new ArrayList<Player>();
@ -497,7 +499,7 @@ public Iterable<Player> matchPlayers(CommandSender source, String filter)
World sourceWorld = sourcePlayer.getWorld(); World sourceWorld = sourcePlayer.getWorld();
org.bukkit.util.Vector sourceVector org.bukkit.util.Vector sourceVector
= sourcePlayer.getLocation().toVector(); = sourcePlayer.getLocation().toVector();
for (Player player : getServer().getOnlinePlayers()) { for (Player player : getServer().getOnlinePlayers()) {
if (player.getWorld().equals(sourceWorld) if (player.getWorld().equals(sourceWorld)
&& player.getLocation().toVector().distanceSquared( && player.getLocation().toVector().distanceSquared(
@ -507,20 +509,20 @@ public Iterable<Player> matchPlayers(CommandSender source, String filter)
} }
return checkPlayerMatch(players); return checkPlayerMatch(players);
} else { } else {
throw new CommandException("Invalid group '" + filter + "'."); throw new CommandException("Invalid group '" + filter + "'.");
} }
} }
List<Player> players = matchPlayerNames(filter); List<Player> players = matchPlayerNames(filter);
return checkPlayerMatch(players); return checkPlayerMatch(players);
} }
/** /**
* Match only a single player. * Match only a single player.
* *
* @param sender The {@link CommandSender} who is requesting a player match * @param sender The {@link CommandSender} who is requesting a player match
* @param filter The filter string. * @param filter The filter string.
* @see #matchPlayers(org.bukkit.entity.Player) for filter string syntax * @see #matchPlayers(org.bukkit.entity.Player) for filter string syntax
@ -531,9 +533,9 @@ public Player matchSinglePlayer(CommandSender sender, String filter)
throws CommandException { throws CommandException {
// This will throw an exception if there are no matches // This will throw an exception if there are no matches
Iterator<Player> players = matchPlayers(sender, filter).iterator(); Iterator<Player> players = matchPlayers(sender, filter).iterator();
Player match = players.next(); Player match = players.next();
// We don't want to match the wrong person, so fail if if multiple // We don't want to match the wrong person, so fail if if multiple
// players were found (we don't want to just pick off the first one, // players were found (we don't want to just pick off the first one,
// as that may be the wrong player) // as that may be the wrong player)
@ -541,10 +543,10 @@ public Player matchSinglePlayer(CommandSender sender, String filter)
throw new CommandException("More than one player found! " + throw new CommandException("More than one player found! " +
"Use @<name> for exact matching."); "Use @<name> for exact matching.");
} }
return match; return match;
} }
/** /**
* Match only a single player or console. * Match only a single player or console.
* *
@ -558,27 +560,27 @@ public Player matchSinglePlayer(CommandSender sender, String filter)
*/ */
public CommandSender matchPlayerOrConsole(CommandSender sender, String filter) public CommandSender matchPlayerOrConsole(CommandSender sender, String filter)
throws CommandException { throws CommandException {
// Let's see if console is wanted // Let's see if console is wanted
if (filter.equalsIgnoreCase("#console") if (filter.equalsIgnoreCase("#console")
|| filter.equalsIgnoreCase("*console*") || filter.equalsIgnoreCase("*console*")
|| filter.equalsIgnoreCase("!")) { || filter.equalsIgnoreCase("!")) {
return getServer().getConsoleSender(); return getServer().getConsoleSender();
} }
return matchSinglePlayer(sender, filter); return matchSinglePlayer(sender, filter);
} }
/** /**
* Get a single player as an iterator for players. * Get a single player as an iterator for players.
* *
* @param player The player to return in an Iterable * @param player The player to return in an Iterable
* @return iterator for player * @return iterator for player
*/ */
public Iterable<Player> matchPlayers(Player player) { public Iterable<Player> matchPlayers(Player player) {
return Arrays.asList(player); return Arrays.asList(player);
} }
/** /**
* Match a world. * Match a world.
* *
@ -602,7 +604,7 @@ public World matchWorld(CommandSender sender, String filter) throws CommandExcep
// #main for the main world // #main for the main world
if (filter.equalsIgnoreCase("#main")) { if (filter.equalsIgnoreCase("#main")) {
return worlds.get(0); return worlds.get(0);
// #normal for the first normal world // #normal for the first normal world
} else if (filter.equalsIgnoreCase("#normal")) { } else if (filter.equalsIgnoreCase("#normal")) {
for (World world : worlds) { for (World world : worlds) {
@ -612,7 +614,7 @@ public World matchWorld(CommandSender sender, String filter) throws CommandExcep
} }
throw new CommandException("No normal world found."); throw new CommandException("No normal world found.");
// #nether for the first nether world // #nether for the first nether world
} else if (filter.equalsIgnoreCase("#nether")) { } else if (filter.equalsIgnoreCase("#nether")) {
for (World world : worlds) { for (World world : worlds) {
@ -626,30 +628,30 @@ public World matchWorld(CommandSender sender, String filter) throws CommandExcep
// Handle getting a world from a player // Handle getting a world from a player
} else if (filter.matches("^#player$")) { } else if (filter.matches("^#player$")) {
String parts[] = filter.split(":", 2); String parts[] = filter.split(":", 2);
// They didn't specify an argument for the player! // They didn't specify an argument for the player!
if (parts.length == 1) { if (parts.length == 1) {
throw new CommandException("Argument expected for #player."); throw new CommandException("Argument expected for #player.");
} }
return matchPlayers(sender, parts[1]).iterator().next().getWorld(); return matchPlayers(sender, parts[1]).iterator().next().getWorld();
} else { } else {
throw new CommandException("Invalid identifier '" + filter + "'."); throw new CommandException("Invalid identifier '" + filter + "'.");
} }
} }
for (World world : worlds) { for (World world : worlds) {
if (world.getName().equals(filter)) { if (world.getName().equals(filter)) {
return world; return world;
} }
} }
throw new CommandException("No world by that exact name found."); throw new CommandException("No world by that exact name found.");
} }
/** /**
* Gets a copy of the WorldEdit plugin. * Gets a copy of the WorldEdit plugin.
* *
* @return The WorldEditPlugin instance * @return The WorldEditPlugin instance
* @throws CommandException If there is no WorldEditPlugin available * @throws CommandException If there is no WorldEditPlugin available
*/ */
@ -658,33 +660,33 @@ public WorldEditPlugin getWorldEdit() throws CommandException {
if (worldEdit == null) { if (worldEdit == null) {
throw new CommandException("WorldEdit does not appear to be installed."); throw new CommandException("WorldEdit does not appear to be installed.");
} }
if (worldEdit instanceof WorldEditPlugin) { if (worldEdit instanceof WorldEditPlugin) {
return (WorldEditPlugin) worldEdit; return (WorldEditPlugin) worldEdit;
} else { } else {
throw new CommandException("WorldEdit detection failed (report error)."); throw new CommandException("WorldEdit detection failed (report error).");
} }
} }
/** /**
* Wrap a player as a LocalPlayer. * Wrap a player as a LocalPlayer.
* *
* @param player The player to wrap * @param player The player to wrap
* @return The wrapped player * @return The wrapped player
*/ */
public LocalPlayer wrapPlayer(Player player) { public LocalPlayer wrapPlayer(Player player) {
return new BukkitPlayer(this, player); return new BukkitPlayer(this, player);
} }
/** /**
* Create a default configuration file from the .jar. * Create a default configuration file from the .jar.
* *
* @param actual The destination file * @param actual The destination file
* @param defaultName The name of the file inside the jar's defaults folder * @param defaultName The name of the file inside the jar's defaults folder
*/ */
public void createDefaultConfiguration(File actual, public void createDefaultConfiguration(File actual,
String defaultName) { String defaultName) {
// Make parent directories // Make parent directories
File parent = actual.getParentFile(); File parent = actual.getParentFile();
if (!parent.exists()) { if (!parent.exists()) {
@ -705,7 +707,7 @@ public void createDefaultConfiguration(File actual,
} catch (IOException e) { } catch (IOException e) {
getLogger().severe("Unable to read default configuration: " + defaultName); getLogger().severe("Unable to read default configuration: " + defaultName);
} }
if (input != null) { if (input != null) {
FileOutputStream output = null; FileOutputStream output = null;
@ -738,9 +740,9 @@ public void createDefaultConfiguration(File actual,
} }
} }
} }
/** /**
* Notifies all with the notify permission. * Notifies all with the worldguard.notify permission.
* This will check both superperms and WEPIF, * This will check both superperms and WEPIF,
* but makes sure WEPIF checks don't result in duplicate notifications * but makes sure WEPIF checks don't result in duplicate notifications
* *
@ -748,28 +750,29 @@ public void createDefaultConfiguration(File actual,
*/ */
public void broadcastNotification(String msg) { public void broadcastNotification(String msg) {
getServer().broadcast(msg, "worldguard.notify"); getServer().broadcast(msg, "worldguard.notify");
Set<Permissible> subs = getServer().getPluginManager().getPermissionSubscriptions("worldguard.notify");
for (Player player : getServer().getOnlinePlayers()) { for (Player player : getServer().getOnlinePlayers()) {
if (hasPermission(player, "worldguard.notify") && if (!subs.contains(player) &&
!player.hasPermission("worldguard.notify")) { // Make sure the player wasn't already broadcasted to. hasPermission(player, "worldguard.notify")) { // Make sure the player wasn't already broadcasted to.
player.sendMessage(msg); player.sendMessage(msg);
} }
} }
getLogger().info(msg); getLogger().info(msg);
} }
/** /**
* Forgets a player. * Forgets a player.
* *
* @param player The player to remove state information for * @param player The player to remove state information for
*/ */
public void forgetPlayer(Player player) { public void forgetPlayer(Player player) {
flagStateManager.forget(player); flagStateManager.forget(player);
} }
/** /**
* Checks to see if a player can build at a location. This will return * Checks to see if a player can build at a location. This will return
* true if region protection is disabled. * true if region protection is disabled.
* *
* @param player The player to check. * @param player The player to check.
* @param loc The location to check at. * @param loc The location to check at.
* @see GlobalRegionManager#canBuild(org.bukkit.entity.Player, org.bukkit.Location) * @see GlobalRegionManager#canBuild(org.bukkit.entity.Player, org.bukkit.Location)
@ -778,11 +781,11 @@ public void forgetPlayer(Player player) {
public boolean canBuild(Player player, Location loc) { public boolean canBuild(Player player, Location loc) {
return getGlobalRegionManager().canBuild(player, loc); return getGlobalRegionManager().canBuild(player, loc);
} }
/** /**
* Checks to see if a player can build at a location. This will return * Checks to see if a player can build at a location. This will return
* true if region protection is disabled. * true if region protection is disabled.
* *
* @param player The player to check * @param player The player to check
* @param block The block to check at. * @param block The block to check at.
* @see GlobalRegionManager#canBuild(org.bukkit.entity.Player, org.bukkit.block.Block) * @see GlobalRegionManager#canBuild(org.bukkit.entity.Player, org.bukkit.block.Block)