Merge pull request #2069 from BentoBoxWorld/develop

New version
This commit is contained in:
tastybento 2023-02-12 15:00:01 -08:00 committed by GitHub
commit fe888a8d0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
194 changed files with 7109 additions and 2620 deletions

49
pom.xml
View File

@ -66,23 +66,29 @@
<java.version>17</java.version>
<!-- Non-minecraft related dependencies -->
<powermock.version>2.0.9</powermock.version>
<!-- Database related dependencies -->
<mongodb.version>3.12.8</mongodb.version>
<mariadb.version>3.0.5</mariadb.version>
<mysql.version>8.0.27</mysql.version>
<postgresql.version>42.2.18</postgresql.version>
<hikaricp.version>5.0.1</hikaricp.version>
<!-- More visible way to change dependency versions -->
<spigot.version>1.19.2-R0.1-SNAPSHOT</spigot.version>
<spigot.version>1.19.3-R0.1-SNAPSHOT</spigot.version>
<!-- Might differ from the last Spigot release for short periods
of time -->
<paper.version>1.19-R0.1-SNAPSHOT</paper.version>
<bstats.version>2.2.1</bstats.version>
<vault.version>1.7</vault.version>
<bstats.version>3.0.0</bstats.version>
<vault.version>1.7.1</vault.version>
<placeholderapi.version>2.10.9</placeholderapi.version>
<githubapi.version>d5f5e0bbd8</githubapi.version>
<dynmap.version>3.0-SNAPSHOT</dynmap.version>
<myworlds.version>1.19.3-v1</myworlds.version>
<!-- Revision variable removes warning about dynamic version -->
<revision>${build.version}-SNAPSHOT</revision>
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.21.1</build.version>
<build.version>1.22.0</build.version>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
</properties>
@ -176,6 +182,11 @@
<id>nms-repo</id>
<url>https://repo.codemc.io/repository/nms/</url>
</repository>
<!-- Used for MyWorlds hook -->
<repository>
<id>MG-Dev Jenkins CI Maven Repository</id>
<url>https://ci.mg-dev.eu/plugin/repository/everything</url>
</repository>
</repositories>
<dependencies>
@ -197,7 +208,7 @@
<dependency>
<groupId>com.mojang</groupId>
<artifactId>authlib</artifactId>
<version>3.2.38</version>
<version>3.16.29</version>
<scope>provided</scope>
</dependency>
<!-- Metrics -->
@ -232,10 +243,11 @@
<version>${mongodb.version}</version>
<scope>provided</scope>
</dependency>
<!-- HikariCP database handler -->
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901-1.jdbc4</version>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
<scope>provided</scope>
</dependency>
<!-- Vault: as their maven repo is down, we need to get it from jitpack -->
@ -260,6 +272,12 @@
<version>${dynmap.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.bergerkiller.bukkit</groupId>
<artifactId>MyWorlds</artifactId>
<version>${myworlds.version}</version>
<scope>provided</scope>
</dependency>
<!-- Shaded APIs -->
<dependency>
<groupId>com.github.TheBusyBiscuit</groupId>
@ -269,7 +287,7 @@
<dependency>
<groupId>com.github.Marcono1234</groupId>
<artifactId>gson-record-type-adapter-factory</artifactId>
<version>0.1.0</version>
<version>0.3.0</version>
</dependency>
<!-- Static analysis -->
<!-- We are using Eclipse's annotations. If you're using IDEA, update
@ -352,6 +370,7 @@
<version>3.0.0-M5</version>
<configuration>
<argLine>
${argLine}
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
@ -390,10 +409,11 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version>
<version>3.4.1</version>
<configuration>
<source>${java.version}</source>
<show>private</show>
<quiet>true</quiet>
<failOnError>false</failOnError>
<additionalJOption>-Xdoclint:none</additionalJOption>
<!-- To compile with Java 11, this tag may be required -->
@ -495,16 +515,21 @@
</configuration>
<executions>
<execution>
<id>pre-unit-test</id>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>post-unit-test</id>
<id>report</id>
<goals>
<goal>report</goal>
</goals>
<configuration>
<formats>
<format>XML</format>
</formats>
</configuration>
</execution>
</executions>
</plugin>

View File

@ -20,6 +20,7 @@ import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.commands.BentoBoxCommand;
import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.hooks.MultiverseCoreHook;
import world.bentobox.bentobox.hooks.MyWorldsHook;
import world.bentobox.bentobox.hooks.VaultHook;
import world.bentobox.bentobox.hooks.placeholders.PlaceholderAPIHook;
import world.bentobox.bentobox.listeners.BannedCommands;
@ -27,9 +28,9 @@ import world.bentobox.bentobox.listeners.BlockEndDragon;
import world.bentobox.bentobox.listeners.DeathListener;
import world.bentobox.bentobox.listeners.JoinLeaveListener;
import world.bentobox.bentobox.listeners.PanelListenerManager;
import world.bentobox.bentobox.listeners.StandardSpawnProtectionListener;
import world.bentobox.bentobox.listeners.teleports.EntityTeleportListener;
import world.bentobox.bentobox.listeners.teleports.PlayerTeleportListener;
import world.bentobox.bentobox.listeners.StandardSpawnProtectionListener;
import world.bentobox.bentobox.managers.AddonsManager;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.CommandsManager;
@ -225,6 +226,7 @@ public class BentoBox extends JavaPlugin {
// Register Multiverse hook - MV loads AFTER BentoBox
// Make sure all worlds are already registered to Multiverse.
hooksManager.registerHook(new MultiverseCoreHook());
hooksManager.registerHook(new MyWorldsHook());
islandWorldManager.registerWorldsToMultiverse();
// TODO: re-enable after implementation

View File

@ -1,6 +1,8 @@
package world.bentobox.bentobox;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.bukkit.Material;
@ -11,8 +13,10 @@ import world.bentobox.bentobox.api.configuration.ConfigObject;
import world.bentobox.bentobox.api.configuration.StoreAt;
import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType;
/**
* All the plugin settings are here
*
* @author tastybento
*/
@StoreAt(filename="config.yml") // Explicitly call out what name this should have.
@ -68,6 +72,7 @@ public class Settings implements ConfigObject {
@ConfigComment("Transition options enable migration from one database type to another. Use /bbox migrate.")
@ConfigComment("YAML and JSON are file-based databases.")
@ConfigComment("MYSQL might not work with all implementations: if available, use a dedicated database type (e.g. MARIADB).")
@ConfigComment("BentoBox uses HikariCP for connecting with SQL databases.")
@ConfigComment("If you use MONGODB, you must also run the BSBMongo plugin (not addon).")
@ConfigComment("See https://github.com/tastybento/bsbMongo/releases/.")
@ConfigEntry(path = "general.database.type", video = "https://youtu.be/FFzCk5-y7-g")
@ -107,6 +112,11 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "general.database.max-saved-islands-per-tick")
private int maxSavedIslandsPerTick = 20;
@ConfigComment("Number of active connections to the SQL database at the same time.")
@ConfigComment("Default 10.")
@ConfigEntry(path = "general.database.max-pool-size", since = "1.21.0")
private int maximumPoolSize = 10;
@ConfigComment("Enable SSL connection to MongoDB, MariaDB, MySQL and PostgreSQL databases.")
@ConfigEntry(path = "general.database.use-ssl", since = "1.12.0")
private boolean useSSL = false;
@ -118,6 +128,16 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "general.database.prefix-character", since = "1.13.0")
private String databasePrefix = "";
@ConfigComment("Custom connection datasource properties that will be applied to connection pool.")
@ConfigComment("Check available values to your SQL driver implementation.")
@ConfigComment("Example: ")
@ConfigComment(" custom-properties: ")
@ConfigComment(" cachePrepStmts: 'true'")
@ConfigComment(" prepStmtCacheSize: '250'")
@ConfigComment(" prepStmtCacheSqlLimit: '2048'")
@ConfigEntry(path = "general.database.custom-properties", since = "1.21.0")
private Map<String, String> customPoolProperties = new HashMap<>();
@ConfigComment("MongoDB client connection URI to override default connection options.")
@ConfigComment("See: https://docs.mongodb.com/manual/reference/connection-string/")
@ConfigEntry(path = "general.database.mongodb-connection-uri", since = "1.14.0")
@ -954,6 +974,17 @@ public class Settings implements ConfigObject {
}
/**
* Gets maximum pool size.
*
* @return the maximum pool size
*/
public int getMaximumPoolSize()
{
return maximumPoolSize;
}
/**
* Gets safe spot search range.
*
@ -965,6 +996,39 @@ public class Settings implements ConfigObject {
}
/**
* Sets maximum pool size.
*
* @param maximumPoolSize the maximum pool size
*/
public void setMaximumPoolSize(int maximumPoolSize)
{
this.maximumPoolSize = maximumPoolSize;
}
/**
* Gets custom pool properties.
*
* @return the custom pool properties
*/
public Map<String, String> getCustomPoolProperties()
{
return customPoolProperties;
}
/**
* Sets custom pool properties.
*
* @param customPoolProperties the custom pool properties
*/
public void setCustomPoolProperties(Map<String, String> customPoolProperties)
{
this.customPoolProperties = customPoolProperties;
}
/**
* Sets safe spot search range.
*

View File

@ -7,6 +7,7 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@ -33,6 +34,19 @@ public class AddonClassLoader extends URLClassLoader {
private final Addon addon;
private final AddonsManager loader;
/**
* For testing only
* @param addon addon
* @param loader Addons Manager
* @param jarFile Jar File
* @throws MalformedURLException exception
*/
protected AddonClassLoader(Addon addon, AddonsManager loader, File jarFile) throws MalformedURLException {
super(new URL[]{jarFile.toURI().toURL()});
this.addon = addon;
this.loader = loader;
}
public AddonClassLoader(AddonsManager addonsManager, YamlConfiguration data, File jarFile, ClassLoader parent)
throws InvalidAddonInheritException,
MalformedURLException,
@ -79,8 +93,27 @@ public class AddonClassLoader extends URLClassLoader {
*/
@NonNull
public static AddonDescription asDescription(YamlConfiguration data) throws InvalidAddonDescriptionException {
AddonDescription.Builder builder = new AddonDescription.Builder(Objects.requireNonNull(data.getString("main")), Objects.requireNonNull(data.getString("name")), Objects.requireNonNull(data.getString("version")))
// Validate addon.yml
if (!data.contains("main")) {
throw new InvalidAddonDescriptionException("Missing 'main' tag. A main class must be listed in addon.yml");
}
if (!data.contains("name")) {
throw new InvalidAddonDescriptionException("Missing 'name' tag. An addon name must be listed in addon.yml");
}
if (!data.contains("version")) {
throw new InvalidAddonDescriptionException("Missing 'version' tag. A version must be listed in addon.yml");
}
if (!data.contains("authors")) {
throw new InvalidAddonDescriptionException("Missing 'authors' tag. At least one author must be listed in addon.yml");
}
AddonDescription.Builder builder = new AddonDescription.Builder(
// Mandatory elements
Objects.requireNonNull(data.getString("main")),
Objects.requireNonNull(data.getString("name")),
Objects.requireNonNull(data.getString("version")))
.authors(Objects.requireNonNull(data.getString("authors")))
// Optional elements
.metrics(data.getBoolean("metrics", true))
.repository(data.getString("repository", ""));
@ -92,7 +125,7 @@ public class AddonClassLoader extends URLClassLoader {
if (softDepend != null) {
builder.softDependencies(Arrays.asList(softDepend.split("\\s*,\\s*")));
}
builder.icon(Objects.requireNonNull(Material.getMaterial(data.getString("icon", "PAPER"))));
builder.icon(Objects.requireNonNull(Material.getMaterial(data.getString("icon", "PAPER").toUpperCase(Locale.ENGLISH))));
String apiVersion = data.getString("api-version");
if (apiVersion != null) {

View File

@ -287,7 +287,7 @@ public final class AddonDescription {
*/
@Override
public String toString() {
return "AddonDescription [" + (name != null ? "name=" + name + ", " : "")
return "AddonDescription [" + "name=" + name + ", "
+ "version=" + version + "]";
}
}

View File

@ -11,7 +11,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.bukkit.World;
import org.bukkit.command.Command;
@ -514,18 +513,6 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
return onlyPlayer;
}
/**
* Convenience method to check if a user is a player
* @param user - the User
* @return true if sender is a player
* @deprecated use {@link User#isPlayer()}
* @forRemove 1.18.0
*/
@Deprecated
protected boolean isPlayer(User user) {
return user.isPlayer();
}
/**
* Sets whether this command should only be run by players.
* If this is set to {@code true}, this command will only be runnable by objects implementing {@link Player}.
@ -663,7 +650,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/* ------------ */
String lastArg = args.length != 0 ? args[args.length - 1] : "";
return Util.tabLimit(options, lastArg).stream().sorted().collect(Collectors.toList());
return Util.tabLimit(options, lastArg).stream().sorted().toList();
}
/**
@ -677,7 +664,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
return command.getSubCommands().values().stream()
.filter(cmd -> !cmd.isHidden())
.filter(cmd -> !cmd.isOnlyPlayer() || sender.isOp() || (sender instanceof Player && cmd.getPermission() != null && (cmd.getPermission().isEmpty() || sender.hasPermission(cmd.getPermission()))) )
.map(CompositeCommand::getLabel).collect(Collectors.toList());
.map(CompositeCommand::getLabel).toList();
}
/**

View File

@ -83,7 +83,7 @@ public class AdminDeleteCommand extends ConfirmableCommand {
// Remove them from this island (it still exists and will be deleted later)
getIslands().removePlayer(getWorld(), targetUUID);
if (target.isPlayer() && target.isOnline()) {
cleanUp(user, target);
cleanUp(target);
}
vector = oldIsland.getCenter().toVector();
getIslands().deleteIsland(oldIsland, true, targetUUID);
@ -95,7 +95,7 @@ public class AdminDeleteCommand extends ConfirmableCommand {
}
}
private void cleanUp(User user, User target) {
private void cleanUp(User target) {
// Remove money inventory etc.
if (getIWM().isOnLeaveResetEnderChest(getWorld())) {
target.getPlayer().getEnderChest().clear();
@ -122,7 +122,7 @@ public class AdminDeleteCommand extends ConfirmableCommand {
}
// Execute commands when leaving
Util.runCommands(target, getIWM().getOnLeaveCommands(getWorld()), "leave");
Util.runCommands(target, target.getName(), getIWM().getOnLeaveCommands(getWorld()), "leave");
}
@Override

View File

@ -55,11 +55,11 @@ public class AdminDeleteHomesCommand extends ConfirmableCommand {
return false;
}
// Confirm
askConfirmation(user, user.getTranslation("commands.admin.deletehomes.warning"), () -> deleteHomes(user, targetUUID, island));
askConfirmation(user, user.getTranslation("commands.admin.deletehomes.warning"), () -> deleteHomes(user, island));
return true;
}
private boolean deleteHomes(User user, UUID targetUUID, Island island) {
private boolean deleteHomes(User user, Island island) {
island.removeHomes();
user.sendMessage("general.success");
return true;

View File

@ -3,7 +3,6 @@ package world.bentobox.bentobox.api.commands.admin;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@ -98,7 +97,7 @@ public class AdminGetrankCommand extends CompositeCommand {
return Optional.empty();
}
String lastArg = args.get(args.size() - 1);
List<String> options = Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList());
List<String> options = Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
return Optional.of(Util.tabLimit(options, lastArg));
}
}

View File

@ -3,7 +3,6 @@ package world.bentobox.bentobox.api.commands.admin;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
@ -26,7 +25,7 @@ public class AdminResetFlagsCommand extends ConfirmableCommand {
super(parent, "resetflags");
options = getPlugin().getFlagsManager().getFlags().stream()
.filter(f -> f.getType().equals(Type.PROTECTION) || f.getType().equals(Type.SETTING))
.map(Flag::getID).collect(Collectors.toList());
.map(Flag::getID).toList();
}
@Override

View File

@ -1,10 +1,11 @@
package world.bentobox.bentobox.api.commands.admin;
import org.eclipse.jdt.annotation.Nullable;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;

View File

@ -50,7 +50,7 @@ public class AdminSetProtectionCenterCommand extends ConfirmableCommand
public boolean canExecute(User user, String label, List<String> args) {
if (args.size() == 3) {
// Get location
targetLoc = getLocation(user, args);
targetLoc = getLocation(args);
} else {
targetLoc = new Location(getWorld(), user.getLocation().getBlockX(), user.getLocation().getBlockY(), user.getLocation().getBlockZ());
}
@ -67,7 +67,7 @@ public class AdminSetProtectionCenterCommand extends ConfirmableCommand
return true;
}
private Location getLocation(User user, List<String> args) {
private Location getLocation(List<String> args) {
try {
int x = Integer.parseInt(args.get(0));
int y = Integer.parseInt(args.get(1));

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.Nullable;
@ -140,7 +139,7 @@ public class AdminSetrankCommand extends CompositeCommand {
return Optional.of(getPlugin().getRanksManager().getRanks()
.entrySet().stream()
.filter(entry -> entry.getValue() > RanksManager.VISITOR_RANK)
.map(entry -> user.getTranslation(entry.getKey())).collect(Collectors.toList()));
.map(entry -> user.getTranslation(entry.getKey())).toList());
}
// Return the player names again for the optional island owner argument

View File

@ -8,7 +8,6 @@ import java.util.Locale;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.ChatColor;
import org.eclipse.jdt.annotation.NonNull;
@ -88,12 +87,10 @@ public class AdminSettingsCommand extends CompositeCommand {
}
private boolean getIsland(User user, List<String> args) {
if (args.get(0).equalsIgnoreCase(SPAWN_ISLAND)) {
if (getIslands().getSpawn(getWorld()).isPresent()) {
if (args.get(0).equalsIgnoreCase(SPAWN_ISLAND) && getIslands().getSpawn(getWorld()).isPresent()) {
island = getIslands().getSpawn(getWorld()).get();
return true;
}
}
// Get target player
@Nullable UUID targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
@ -192,19 +189,18 @@ public class AdminSettingsCommand extends CompositeCommand {
// Command line setting
flag.ifPresent(f -> {
switch (f.getType()) {
case PROTECTION:
case PROTECTION -> {
island.setFlag(f, rank);
getIslands().save(island);
break;
case SETTING:
}
case SETTING -> {
island.setSettingsFlag(f, activeState);
getIslands().save(island);
break;
case WORLD_SETTING:
f.setSetting(getWorld(), activeState);
break;
default:
break;
}
case WORLD_SETTING -> f.setSetting(getWorld(), activeState);
default -> {
// Do nothing
}
}
});
user.sendMessage("general.success");
@ -270,7 +266,7 @@ public class AdminSettingsCommand extends CompositeCommand {
.getRanks().entrySet().stream()
.filter(en -> en.getValue() > RanksManager.BANNED_RANK && en.getValue() <= RanksManager.OWNER_RANK)
.map(Entry::getKey)
.map(user::getTranslation).collect(Collectors.toList());
.map(user::getTranslation).toList();
case SETTING -> Arrays.asList(active, disabled);
default -> Collections.<String>emptyList();
}).orElse(Collections.emptyList());

View File

@ -9,7 +9,12 @@ import world.bentobox.bentobox.api.commands.admin.deaths.AdminDeathsCommand;
import world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand;
import world.bentobox.bentobox.api.commands.admin.range.AdminRangeCommand;
import world.bentobox.bentobox.api.commands.admin.resets.AdminResetsCommand;
import world.bentobox.bentobox.api.commands.admin.team.*;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamAddCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamDisbandCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamFixCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamKickCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamSetownerCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
@ -25,7 +30,7 @@ public abstract class DefaultAdminCommand extends CompositeCommand {
*
* @param addon - GameMode addon
*/
public DefaultAdminCommand(GameModeAddon addon) {
protected DefaultAdminCommand(GameModeAddon addon) {
// Register command with alias from config.
super(addon,
addon.getWorldSettings().getAdminCommandAliases().split(" ")[0],

View File

@ -2,7 +2,6 @@ package world.bentobox.bentobox.api.commands.admin.blueprints;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;

View File

@ -2,13 +2,11 @@ package world.bentobox.bentobox.api.commands.admin.blueprints;
import java.io.File;
import java.util.List;
import java.util.Locale;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.blueprints.BlueprintClipboard;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.util.Util;

View File

@ -51,7 +51,7 @@ public class AdminBlueprintSaveCommand extends ConfirmableCommand
return false;
}
if (clipboard.getBlueprint().getBedrock() == null)
if (clipboard.getBlueprint() != null && clipboard.getBlueprint().getBedrock() == null)
{
// Bedrock is required for all blueprints.
user.sendMessage("commands.admin.blueprint.bedrock-required");

View File

@ -17,8 +17,10 @@ import world.bentobox.bentobox.database.objects.Island;
*/
public class NamePrompt extends StringPrompt {
private @NonNull final Island island;
private @NonNull final User user;
@NonNull
private final Island island;
@NonNull
private final User user;
private final String oldName;
private final BentoBox plugin;
@ -30,7 +32,8 @@ public class NamePrompt extends StringPrompt {
}
@Override
public @NonNull String getPromptText(@NonNull ConversationContext context) {
@NonNull
public String getPromptText(@NonNull ConversationContext context) {
return user.getTranslation("commands.island.renamehome.enter-new-name");
}

View File

@ -99,10 +99,10 @@ public class AdminRangeDisplayCommand extends CompositeCommand {
// Draw 3 "stages" (one line below, at and above player's y coordinate)
for (int stage = -1 ; stage <= 1 ; stage++) {
for (int i = -range ; i <= range ; i++) {
user.spawnParticle(particle, dustOptions, center.getBlockX() + i, playerY + stage, center.getBlockZ() + range);
user.spawnParticle(particle, dustOptions, center.getBlockX() + i, playerY + stage, center.getBlockZ() - range);
user.spawnParticle(particle, dustOptions, center.getBlockX() + range, playerY + stage, center.getBlockZ() + i);
user.spawnParticle(particle, dustOptions, center.getBlockX() - range, playerY + stage, center.getBlockZ() + i);
user.spawnParticle(particle, dustOptions, (double)center.getBlockX() + i, (double)playerY + stage, (double)center.getBlockZ() + range);
user.spawnParticle(particle, dustOptions, (double)center.getBlockX() + i, (double)playerY + stage, (double)center.getBlockZ() - range);
user.spawnParticle(particle, dustOptions, (double)center.getBlockX() + range, (double)playerY + stage, (double)center.getBlockZ() + i);
user.spawnParticle(particle, dustOptions, (double)center.getBlockX() - range, (double)playerY + stage, (double)center.getBlockZ() + i);
}
}
}

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
@ -142,7 +141,7 @@ public class IslandBanCommand extends CompositeCommand {
.filter(p -> !p.getUniqueId().equals(user.getUniqueId()))
.filter(p -> !island.isBanned(p.getUniqueId()))
.filter(p -> user.getPlayer().canSee(p))
.map(Player::getName).collect(Collectors.toList());
.map(Player::getName).toList();
return Optional.of(Util.tabLimit(options, lastArg));
} else {
return Optional.empty();

View File

@ -3,7 +3,6 @@ package world.bentobox.bentobox.api.commands.island;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
@ -57,7 +56,7 @@ public class IslandBanlistCommand extends CompositeCommand {
// Title
user.sendMessage("commands.island.banlist.the-following");
// Create a nicely formatted list
List<String> names = island.getBanned().stream().map(u -> getPlayers().getName(u)).sorted().collect(Collectors.toList());
List<String> names = island.getBanned().stream().map(u -> getPlayers().getName(u)).sorted().toList();
List<String> lines = new ArrayList<>();
StringBuilder line = new StringBuilder();
// Put the names into lines of no more than 40 characters long, separated by commas

View File

@ -84,9 +84,9 @@ public class IslandDeletehomeCommand extends ConfirmableCommand {
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
if (island != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(island.getHomes().keySet()), lastArg));
Island is = getIslands().getIsland(getWorld(), user.getUniqueId());
if (is != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(is.getHomes().keySet()), lastArg));
} else {
return Optional.empty();
}

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
@ -156,7 +155,7 @@ public class IslandExpelCommand extends CompositeCommand {
.filter(p -> !p.isOp()) // Not op
.filter(p -> !p.hasPermission(this.getPermissionPrefix() + "admin.noexpel"))
.filter(p -> !p.hasPermission(this.getPermissionPrefix() + "mod.bypassexpel"))
.map(Player::getName).collect(Collectors.toList());
.map(Player::getName).toList();
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
return Optional.of(Util.tabLimit(options, lastArg));

View File

@ -55,14 +55,12 @@ public class IslandGoCommand extends DelayedTeleportCommand {
user.sendMessage(Flags.PREVENT_TELEPORT_WHEN_FALLING.getHintReference());
return false;
}
if (!args.isEmpty()) {
if (!getIslands().isHomeLocation(island, String.join(" ", args))) {
if (!args.isEmpty() && !getIslands().isHomeLocation(island, String.join(" ", args))) {
user.sendMessage("commands.island.go.unknown-home");
user.sendMessage("commands.island.sethome.homes-are");
island.getHomes().keySet().stream().filter(s -> !s.isEmpty()).forEach(s -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, s));
return false;
}
}
return true;
}

View File

@ -87,9 +87,9 @@ public class IslandRenamehomeCommand extends ConfirmableCommand {
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
if (island != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(island.getHomes().keySet()), lastArg));
Island is = getIslands().getIsland(getWorld(), user.getUniqueId());
if (is != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(is.getHomes().keySet()), lastArg));
} else {
return Optional.empty();
}

View File

@ -187,7 +187,7 @@ public class IslandResetCommand extends ConfirmableCommand {
getIslands().removePlayer(getWorld(), memberUUID);
// Clean player
getPlayers().cleanLeavingPlayer(getWorld(), member, false);
getPlayers().cleanLeavingPlayer(getWorld(), member, false, island);
// Fire event
TeamEvent.builder()

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.Nullable;
@ -112,7 +111,7 @@ public class IslandUnbanCommand extends CompositeCommand {
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
if (island != null) {
List<String> options = island.getBanned().stream().map(getPlayers()::getName).collect(Collectors.toList());
List<String> options = island.getBanned().stream().map(getPlayers()::getName).toList();
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
return Optional.of(Util.tabLimit(options, lastArg));
} else {

View File

@ -7,7 +7,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@ -91,12 +90,11 @@ public class IslandTeamCommand extends CompositeCommand {
private void showMembers(Island island, User user) {
// Gather online members
List<UUID> onlineMembers = island
long count = island
.getMemberSet(RanksManager.MEMBER_RANK)
.stream()
.filter(uuid -> Bukkit.getOfflinePlayer(uuid)
.isOnline())
.collect(Collectors.toList());
.filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName()))
.count();
// List of ranks that we will loop through
Integer[] ranks = new Integer[]{RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK, RanksManager.MEMBER_RANK, RanksManager.TRUSTED_RANK, RanksManager.COOP_RANK};
@ -105,11 +103,11 @@ public class IslandTeamCommand extends CompositeCommand {
user.sendMessage("commands.island.team.info.header",
"[max]", String.valueOf(getIslands().getMaxMembers(island, RanksManager.MEMBER_RANK)),
"[total]", String.valueOf(island.getMemberSet().size()),
"[online]", String.valueOf(onlineMembers.size()));
"[online]", String.valueOf(count));
// We now need to get all online "members" of the island - incl. Trusted and coop
onlineMembers = island.getMemberSet(RanksManager.COOP_RANK).stream()
.filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName())).collect(Collectors.toList());
List<UUID> onlineMembers = island.getMemberSet(RanksManager.COOP_RANK).stream()
.filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName())).toList();
for (int rank : ranks) {
Set<UUID> players = island.getMemberSet(rank, false);
@ -123,6 +121,12 @@ public class IslandTeamCommand extends CompositeCommand {
TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)),
TextVariables.NUMBER, String.valueOf(island.getMemberSet(rank, false).size()));
}
displayOnOffline(user, rank, island, onlineMembers);
}
}
}
private void displayOnOffline(User user, int rank, Island island, List<UUID> onlineMembers) {
for (UUID member : island.getMemberSet(rank, false)) {
OfflinePlayer offlineMember = Bukkit.getOfflinePlayer(member);
if (onlineMembers.contains(member)) {
@ -155,15 +159,14 @@ public class IslandTeamCommand extends CompositeCommand {
user.sendMessage("commands.island.team.info.member-layout.offline",
TextVariables.NAME, offlineMember.getName(),
"[last_seen]", lastSeen);
}else{
} else {
// This will prevent anyone that is trusted or below to not have a last-seen status
user.sendMessage("commands.island.team.info.member-layout.offline-not-last-seen",
TextVariables.NAME, offlineMember.getName());
}
}
}
}
}
}
private boolean fireEvent(User user) {

View File

@ -96,7 +96,6 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
// Remove the invite
itc.removeInvite(playerUUID);
User inviter = User.getInstance(invite.getInviter());
if (inviter != null) {
Island island = getIslands().getIsland(getWorld(), inviter);
if (island != null) {
if (island.getMemberSet(RanksManager.TRUSTED_RANK, false).size() > getIslands().getMaxMembers(island, RanksManager.TRUSTED_RANK)) {
@ -111,7 +110,10 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(user), RanksManager.TRUSTED_RANK)
.build();
if (inviter.isOnline()) {
inviter.sendMessage("commands.island.team.trust.success", TextVariables.NAME, user.getName());
}
if (inviter.isPlayer()) {
user.sendMessage("commands.island.team.trust.you-are-trusted", TextVariables.NAME, inviter.getName());
}
}
@ -121,7 +123,6 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
// Remove the invite
itc.removeInvite(playerUUID);
User inviter = User.getInstance(invite.getInviter());
if (inviter != null) {
Island island = getIslands().getIsland(getWorld(), inviter);
if (island != null) {
if (island.getMemberSet(RanksManager.COOP_RANK, false).size() > getIslands().getMaxMembers(island, RanksManager.COOP_RANK)) {
@ -136,7 +137,10 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(user), RanksManager.COOP_RANK)
.build();
if (inviter.isOnline()) {
inviter.sendMessage("commands.island.team.coop.success", TextVariables.NAME, user.getName());
}
if (inviter.isPlayer()) {
user.sendMessage("commands.island.team.coop.you-are-a-coop-member", TextVariables.NAME, inviter.getName());
}
}
@ -153,7 +157,7 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
user.sendMessage(INVALID_INVITE);
return;
}
if (teamIsland.getMemberSet(RanksManager.MEMBER_RANK, true).size() > getIslands().getMaxMembers(teamIsland, RanksManager.MEMBER_RANK)) {
if (teamIsland.getMemberSet(RanksManager.MEMBER_RANK, true).size() >= getIslands().getMaxMembers(teamIsland, RanksManager.MEMBER_RANK)) {
user.sendMessage("commands.island.team.invite.errors.island-is-full");
return;
}
@ -172,6 +176,10 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
// Put player back into normal mode
user.setGameMode(getIWM().getDefaultGameMode(getWorld()));
// Execute commands
String ownerName = this.getPlayers().getName(teamIsland.getOwner());
Util.runCommands(user, ownerName, getIWM().getOnJoinCommands(getWorld()), "join");
});
// Reset deaths
if (getIWM().isTeamJoinDeathReset(getWorld())) {
@ -179,7 +187,7 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
}
user.sendMessage("commands.island.team.invite.accept.you-joined-island", TextVariables.LABEL, getTopLabel());
User inviter = User.getInstance(invite.getInviter());
if (inviter != null) {
if (inviter.isOnline()) {
inviter.sendMessage("commands.island.team.invite.accept.name-joined-your-island", TextVariables.NAME, user.getName());
}
getIslands().save(teamIsland);
@ -224,7 +232,5 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
user.getPlayer().setTotalExperience(0);
}
// Execute commands
Util.runCommands(user, getIWM().getOnJoinCommands(getWorld()), "join");
}
}

View File

@ -69,7 +69,7 @@ public class IslandTeamInviteCommand extends CompositeCommand {
return false;
}
// Check for space on team
if (island.getMemberSet().size() > getIslands().getMaxMembers(island, RanksManager.MEMBER_RANK)) {
if (island.getMemberSet().size() >= getIslands().getMaxMembers(island, RanksManager.MEMBER_RANK)) {
user.sendMessage("commands.island.team.invite.errors.island-is-full");
return false;
}

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@ -103,7 +102,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
getIslands().removePlayer(getWorld(), targetUUID);
// Clean the target player
getPlayers().cleanLeavingPlayer(getWorld(), target, true);
getPlayers().cleanLeavingPlayer(getWorld(), target, true, oldIsland);
user.sendMessage("commands.island.team.kick.success", TextVariables.NAME, target.getName());
IslandEvent.builder()
@ -131,7 +130,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
List<String> options = island.getMemberSet().stream()
.filter(uuid -> island.getRank(uuid) >= RanksManager.MEMBER_RANK)
.map(Bukkit::getOfflinePlayer)
.map(OfflinePlayer::getName).collect(Collectors.toList());
.map(OfflinePlayer::getName).toList();
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
return Optional.of(Util.tabLimit(options, lastArg));

View File

@ -82,7 +82,7 @@ public class IslandTeamLeaveCommand extends ConfirmableCommand {
}
getIslands().setLeaveTeam(getWorld(), user.getUniqueId());
// Clean the player
getPlayers().cleanLeavingPlayer(getWorld(), user, false);
getPlayers().cleanLeavingPlayer(getWorld(), user, false, island);
// Add cooldown for this player and target
if (getSettings().getInviteCooldown() > 0 && getParent() != null) {

View File

@ -3,7 +3,6 @@ package world.bentobox.bentobox.api.commands.island.team;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@ -125,7 +124,7 @@ public class IslandTeamPromoteCommand extends CompositeCommand {
if (island != null) {
List<String> options = island.getMemberSet().stream()
.map(Bukkit::getOfflinePlayer)
.map(OfflinePlayer::getName).collect(Collectors.toList());
.map(OfflinePlayer::getName).toList();
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
return Optional.of(Util.tabLimit(options, lastArg));

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@ -114,7 +113,7 @@ public class IslandTeamUncoopCommand extends CompositeCommand {
List<String> options = island.getMembers().entrySet().stream()
.filter(e -> e.getValue() == RanksManager.COOP_RANK)
.map(e -> Bukkit.getOfflinePlayer(e.getKey()))
.map(OfflinePlayer::getName).collect(Collectors.toList());
.map(OfflinePlayer::getName).toList();
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
return Optional.of(Util.tabLimit(options, lastArg));
} else {

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@ -114,7 +113,7 @@ public class IslandTeamUntrustCommand extends CompositeCommand {
List<String> options = island.getMembers().entrySet().stream()
.filter(e -> e.getValue() == RanksManager.TRUSTED_RANK)
.map(e -> Bukkit.getOfflinePlayer(e.getKey()))
.map(OfflinePlayer::getName).collect(Collectors.toList());
.map(OfflinePlayer::getName).toList();
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
return Optional.of(Util.tabLimit(options, lastArg));
} else {

View File

@ -32,12 +32,12 @@ public interface WorldSettings extends ConfigObject {
/**
* @return default rank settings for new islands
* @deprecated since 1.21
* Map of Flag, Integer does not allow to load other plugin/addon flags.
* @deprecated Map of Flag, Integer does not allow to load other plugin/addon flags.
* It cannot be replaced with Map of String, Integer due to compatibility issues.
* @see WorldSettings#getDefaultIslandFlagNames()
* @since 1.21.0
*/
@Deprecated
@Deprecated(since="1.21.0", forRemoval=true)
Map<Flag, Integer> getDefaultIslandFlags();
/**
@ -57,12 +57,12 @@ public interface WorldSettings extends ConfigObject {
/**
* @return default settings for new
* @deprecated since 1.21
* Map of Flag, Integer does not allow to load other plugin/addon flags.
* @deprecated Map of Flag, Integer does not allow to load other plugin/addon flags.
* It cannot be replaced with Map of String, Integer due to compatibility issues.
* @see WorldSettings#getDefaultIslandSettingNames()
* @since 1.21.0
*/
@Deprecated
@Deprecated(since="1.21.0", forRemoval=true)
Map<Flag, Integer> getDefaultIslandSettings();
/**
@ -70,7 +70,7 @@ public interface WorldSettings extends ConfigObject {
* This is necessary so users could specify any flag names in settings file from other plugins and addons.
* Otherwise, Flag reader would mark flag as invalid and remove it.
* Default implementation is compatibility layer so GameModes that are not upgraded still works.
* @since 1.21
* @since 1.21.0
* @return default settings for new islands.
*/
default Map<String, Integer> getDefaultIslandSettingNames()
@ -288,6 +288,8 @@ public interface WorldSettings extends ConfigObject {
* Available placeholders for the commands are the following:
* <ul>
* <li>{@code [player]}: name of the player</li>
* <li>{@code [owner]}: name of the owner of the island. When joining a team, this will be the team leader's name. When
* creating an island, it is the name of the player</li>
* </ul>
* <br/>
* Here are some examples of valid commands to execute:
@ -345,6 +347,8 @@ public interface WorldSettings extends ConfigObject {
* Available placeholders for the commands are the following:
* <ul>
* <li>{@code [player]}: name of the player</li>
* <li>{@code [owner]}: name of the owner of the island. When joining a team, this will be the team leader's name. When
* creating an island, it is the name of the player</li>
* </ul>
* <br/>
* Here are some examples of valid commands to execute:
@ -363,6 +367,22 @@ public interface WorldSettings extends ConfigObject {
/**
* Returns a list of commands that should be executed when the player respawns after death if {@link Flags#ISLAND_RESPAWN} is true.<br/>
* These commands are executed by the console, unless otherwise stated using the {@code [SUDO]} prefix, in which case they are executed by the player.<br/>
* <br/>
* Available placeholders for the commands are the following:
* <ul>
* <li>{@code [player]}: name of the player</li>
* <li>{@code [owner]}: name of the owner of the island. When joining a team, this will be the team leader's name. When
* creating an island, it is the name of the player</li>
* </ul>
* <br/>
* Here are some examples of valid commands to execute:
* <ul>
* <li>{@code "[SUDO] bbox version"}</li>
* <li>{@code "bsbadmin deaths set [player] 0"}</li>
* </ul>
* <br/>
* Note that player-executed commands might not work, as these commands can be run with said player being offline.
* @return a list of commands.
* @since 1.14.0
* @see #getOnJoinCommands()

View File

@ -53,8 +53,8 @@ public class Flag implements Comparable<Flag> {
*/
WORLD_SETTING(Material.GRASS_BLOCK);
private @NonNull
final Material icon;
@NonNull
private final Material icon;
Type(@NonNull Material icon) {
this.icon = icon;

View File

@ -95,8 +95,8 @@ public abstract class FlagListener implements Listener {
* @param string - translation reference
*/
public void noGo(@NonNull Event e, @NonNull Flag flag, boolean silent, String string) {
if (e instanceof Cancellable) {
((Cancellable)e).setCancelled(true);
if (e instanceof Cancellable cancellable) {
cancellable.setCancelled(true);
}
if (user != null && !silent) {
user.notify(string, TextVariables.DESCRIPTION, user.getTranslation(flag.getHintReference()));
@ -128,7 +128,7 @@ public abstract class FlagListener implements Listener {
// Set user
user = player == null ? null : User.getInstance(player);
if (loc == null) {
if (user != null && user.getLocation() != null && user.getLocation().getWorld() != null) {
if (user != null && user.getLocation().getWorld() != null) {
report(user, e, user.getLocation(), flag, Why.NULL_LOCATION);
}
return true;
@ -144,13 +144,7 @@ public abstract class FlagListener implements Listener {
Optional<Island> island = getIslands().getProtectedIslandAt(loc);
// Handle Settings Flag
if (flag.getType().equals(Flag.Type.SETTING)) {
// If the island exists, return the setting, otherwise return the default setting for this flag
if (island.isPresent()) {
report(user, e, loc, flag, island.map(x -> x.isAllowed(flag)).orElse(false) ? Why.SETTING_ALLOWED_ON_ISLAND : Why.SETTING_NOT_ALLOWED_ON_ISLAND);
} else {
report(user, e, loc, flag, flag.isSetForWorld(loc.getWorld()) ? Why.SETTING_ALLOWED_IN_WORLD : Why.SETTING_NOT_ALLOWED_IN_WORLD);
}
return island.map(x -> x.isAllowed(flag)).orElseGet(() -> flag.isSetForWorld(loc.getWorld()));
return processSetting(flag, island, e, loc);
}
// Protection flag
@ -169,31 +163,14 @@ public abstract class FlagListener implements Listener {
// Handle World Settings
if (flag.getType().equals(Flag.Type.WORLD_SETTING)) {
if (flag.isSetForWorld(loc.getWorld())) {
report(user, e, loc, flag, Why.ALLOWED_IN_WORLD);
return true;
}
report(user, e, loc, flag, Why.NOT_ALLOWED_IN_WORLD);
noGo(e, flag, silent, "protection.world-protected");
return false;
return processWorldSetting(flag, loc, e, silent);
}
// Check if the plugin is set in User (required for testing)
User.setPlugin(plugin);
if (island.isPresent()) {
// If it is not allowed on the island, "bypass island" moderators can do anything
if (island.get().isAllowed(user, flag)) {
report(user, e, loc, flag, Why.RANK_ALLOWED);
return true;
} else if (!user.getMetaData(AdminSwitchCommand.META_TAG).map(MetaDataValue::asBoolean).orElse(false)
&& (user.hasPermission(getIWM().getPermissionPrefix(loc.getWorld()) + "mod.bypass." + flag.getID() + ".island"))) {
report(user, e, loc, flag, Why.BYPASS_ISLAND);
return true;
}
report(user, e, loc, flag, Why.NOT_ALLOWED_ON_ISLAND);
noGo(e, flag, silent, island.get().isSpawn() ? "protection.spawn-protected" : "protection.protected");
return false;
return processBypass(flag, island.get(), e, loc, silent);
}
// The player is in the world, but not on an island, so general world settings apply
if (flag.isSetForWorld(loc.getWorld())) {
@ -206,6 +183,41 @@ public abstract class FlagListener implements Listener {
}
}
private boolean processBypass(@NonNull Flag flag, Island island, @NonNull Event e, @NonNull Location loc, boolean silent) {
// If it is not allowed on the island, "bypass island" moderators can do anything
if (island.isAllowed(user, flag)) {
report(user, e, loc, flag, Why.RANK_ALLOWED);
return true;
} else if (!user.getMetaData(AdminSwitchCommand.META_TAG).map(MetaDataValue::asBoolean).orElse(false)
&& (user.hasPermission(getIWM().getPermissionPrefix(loc.getWorld()) + "mod.bypass." + flag.getID() + ".island"))) {
report(user, e, loc, flag, Why.BYPASS_ISLAND);
return true;
}
report(user, e, loc, flag, Why.NOT_ALLOWED_ON_ISLAND);
noGo(e, flag, silent, island.isSpawn() ? "protection.spawn-protected" : "protection.protected");
return false;
}
private boolean processWorldSetting(@NonNull Flag flag, @NonNull Location loc, @NonNull Event e, boolean silent) {
if (flag.isSetForWorld(loc.getWorld())) {
report(user, e, loc, flag, Why.ALLOWED_IN_WORLD);
return true;
}
report(user, e, loc, flag, Why.NOT_ALLOWED_IN_WORLD);
noGo(e, flag, silent, "protection.world-protected");
return false;
}
private boolean processSetting(@NonNull Flag flag, Optional<Island> island, @NonNull Event e, @NonNull Location loc) {
// If the island exists, return the setting, otherwise return the default setting for this flag
if (island.isPresent()) {
report(user, e, loc, flag, island.map(x -> x.isAllowed(flag)).orElse(false) ? Why.SETTING_ALLOWED_ON_ISLAND : Why.SETTING_NOT_ALLOWED_ON_ISLAND);
} else {
report(user, e, loc, flag, flag.isSetForWorld(loc.getWorld()) ? Why.SETTING_ALLOWED_IN_WORLD : Why.SETTING_NOT_ALLOWED_IN_WORLD);
}
return island.map(x -> x.isAllowed(flag)).orElseGet(() -> flag.isSetForWorld(loc.getWorld()));
}
/**
* Report why something did or did not happen for the admin why command
* @param user user involved
@ -229,7 +241,7 @@ public abstract class FlagListener implements Listener {
.filter(p -> getPlugin().equals(p.getOwningPlugin())).findFirst().map(MetadataValue::asString).orElse("");
if (!issuerUUID.isEmpty()) {
User issuer = User.getInstance(UUID.fromString(issuerUUID));
if (issuer != null && issuer.isPlayer()) {
if (issuer.isPlayer()) {
user.sendRawMessage(whyEvent);
user.sendRawMessage(whyBypass);
}

View File

@ -1,14 +1,15 @@
package world.bentobox.bentobox.api.flags.clicklisteners;
import java.util.Objects;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.event.inventory.ClickType;
import java.util.Objects;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.events.flags.FlagProtectionChangeEvent;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.PanelItem;
@ -57,13 +58,13 @@ public class CycleClick implements PanelItem.ClickHandler {
}
@Override
public boolean onClick(Panel panel, User user, ClickType click, int slot) {
public boolean onClick(Panel panel, User user2, ClickType click, int slot) {
// This click listener is used with TabbedPanel and SettingsTabs only
TabbedPanel tp = (TabbedPanel)panel;
SettingsTab st = (SettingsTab)tp.getActiveTab();
// Get the island for this tab
island = st.getIsland();
this.user = user;
this.user = user2;
changeOccurred = false;
// Permission prefix
String prefix = plugin.getIWM().getPermissionPrefix(Util.getWorld(user.getWorld()));
@ -85,6 +86,35 @@ public class CycleClick implements PanelItem.ClickHandler {
// Rank
int currentRank = island.getFlag(flag);
if (click.equals(ClickType.LEFT)) {
leftClick(flag, rm, currentRank);
} else if (click.equals(ClickType.RIGHT)) {
rightClick(flag, rm, currentRank);
} else if (click.equals(ClickType.SHIFT_LEFT) && user2.isOp()) {
leftShiftClick(flag);
}
});
} else {
reportError();
}
return true;
}
private void reportError() {
if (island == null) {
// Island is not targeted.
user.sendMessage("general.errors.not-on-island");
} else {
// Player is not the allowed to change settings.
user.sendMessage("general.errors.insufficient-rank",
TextVariables.RANK,
user.getTranslation(plugin.getRanksManager().getRank(Objects.requireNonNull(island).getRank(user))));
}
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
}
private void leftClick(Flag flag, RanksManager rm, int currentRank) {
if (currentRank >= maxRank) {
island.setFlag(flag, minRank);
} else {
@ -99,7 +129,10 @@ public class CycleClick implements PanelItem.ClickHandler {
// Fire events for all subflags as well
flag.getSubflags().forEach(subflag -> Bukkit.getPluginManager().callEvent(new FlagProtectionChangeEvent(island, user.getUniqueId(), subflag, island.getFlag(subflag))));
}
} else if (click.equals(ClickType.RIGHT)) {
}
private void rightClick(Flag flag, RanksManager rm, int currentRank) {
if (currentRank <= minRank) {
island.setFlag(flag, maxRank);
} else {
@ -114,7 +147,10 @@ public class CycleClick implements PanelItem.ClickHandler {
// Fire events for all subflags as well
flag.getSubflags().forEach(subflag -> Bukkit.getPluginManager().callEvent(new FlagProtectionChangeEvent(island, user.getUniqueId(), subflag, island.getFlag(subflag))));
}
} else if (click.equals(ClickType.SHIFT_LEFT) && user.isOp()) {
}
private void leftShiftClick(Flag flag) {
if (!plugin.getIWM().getHiddenFlags(user.getWorld()).contains(flag.getID())) {
plugin.getIWM().getHiddenFlags(user.getWorld()).add(flag.getID());
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F);
@ -124,22 +160,7 @@ public class CycleClick implements PanelItem.ClickHandler {
}
// Save changes
plugin.getIWM().getAddon(user.getWorld()).ifPresent(GameModeAddon::saveWorldSettings);
}
});
} else {
if (island == null) {
// Island is not targeted.
user.sendMessage("general.errors.not-on-island");
} else {
// Player is not the allowed to change settings.
user.sendMessage("general.errors.insufficient-rank",
TextVariables.RANK,
user.getTranslation(plugin.getRanksManager().getRank(Objects.requireNonNull(island).getRank(user))));
}
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
}
return true;
}
/**

View File

@ -1,14 +1,15 @@
package world.bentobox.bentobox.api.flags.clicklisteners;
import java.util.Objects;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.event.inventory.ClickType;
import java.util.Objects;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.events.flags.FlagSettingChangeEvent;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler;
@ -60,18 +61,7 @@ public class IslandToggleClick implements ClickHandler {
{
if (click.equals(ClickType.SHIFT_LEFT) && user.isOp())
{
if (!plugin.getIWM().getHiddenFlags(user.getWorld()).contains(flag.getID()))
{
plugin.getIWM().getHiddenFlags(user.getWorld()).add(flag.getID());
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F);
}
else
{
plugin.getIWM().getHiddenFlags(user.getWorld()).remove(flag.getID());
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1F, 1F);
}
// Save changes
plugin.getIWM().getAddon(user.getWorld()).ifPresent(GameModeAddon::saveWorldSettings);
shiftLeftClick(user, flag);
}
else
{
@ -82,6 +72,16 @@ public class IslandToggleClick implements ClickHandler {
user.notify("protection.panel.flag-item.setting-cooldown");
return;
}
toggleFlag(user, flag, island);
}
});
} else {
reportError(user, island);
}
return true;
}
private void toggleFlag(User user, Flag flag, Island island) {
// Toggle flag
island.toggleFlag(flag);
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
@ -102,9 +102,10 @@ public class IslandToggleClick implements ClickHandler {
subflag,
island.isAllowed(subflag))));
}
}
});
} else {
private void reportError(User user, Island island) {
if (island == null) {
user.sendMessage("general.errors.not-on-island");
} else {
@ -115,7 +116,22 @@ public class IslandToggleClick implements ClickHandler {
}
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
}
return true;
private void shiftLeftClick(User user, Flag flag) {
if (!plugin.getIWM().getHiddenFlags(user.getWorld()).contains(flag.getID()))
{
plugin.getIWM().getHiddenFlags(user.getWorld()).add(flag.getID());
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F);
}
else
{
plugin.getIWM().getHiddenFlags(user.getWorld()).remove(flag.getID());
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1F, 1F);
}
// Save changes
plugin.getIWM().getAddon(user.getWorld()).ifPresent(GameModeAddon::saveWorldSettings);
}
}

View File

@ -36,22 +36,22 @@ public class MetaDataValue {
* @param value the value assigned to this metadata value
*/
public MetaDataValue(@NonNull Object value) {
if (value instanceof Integer) {
intValue = (int)value;
} else if (value instanceof Float) {
floatValue = (float)value;
} else if (value instanceof Double) {
doubleValue = (double)value;
} else if (value instanceof Long) {
longValue = (long)value;
} else if (value instanceof Short) {
shortValue = (short)value;
} else if (value instanceof Byte) {
byteValue = (byte)value;
} else if (value instanceof Boolean) {
booleanValue = (boolean)value;
} else if (value instanceof String) {
stringValue = (String)value;
if (value instanceof Integer i) {
intValue = i;
} else if (value instanceof Float f) {
floatValue = f;
} else if (value instanceof Double d) {
doubleValue = d;
} else if (value instanceof Long l) {
longValue = l;
} else if (value instanceof Short s) {
shortValue = s;
} else if (value instanceof Byte b) {
byteValue = b;
} else if (value instanceof Boolean bo) {
booleanValue = bo;
} else if (value instanceof String st) {
stringValue = st;
}
}

View File

@ -24,7 +24,7 @@ public interface Tab {
String getName();
/**
* Return the panel items for this tab
* Return an immutable list of the panel items for this tab
* @return a list of items in slot order
*/
List<@Nullable PanelItem> getPanelItems();

View File

@ -31,8 +31,8 @@ public class TabbedPanel extends Panel implements PanelListener {
private static final String PROTECTION_PANEL = "protection.panel.";
private static final long ITEMS_PER_PAGE = 36;
private final TabbedPanelBuilder tpb;
private @NonNull
final BentoBox plugin = BentoBox.getInstance();
@NonNull
private final BentoBox plugin = BentoBox.getInstance();
private int activeTab;
private int activePage;
private boolean closed;

View File

@ -96,11 +96,11 @@ public class TemplatedPanel extends Panel
{
for (int k = 0; k < this.panelTemplate.content()[i].length; k++)
{
ItemTemplateRecord record = this.panelTemplate.content()[i][k];
ItemTemplateRecord rec = this.panelTemplate.content()[i][k];
if (record != null && record.dataMap().containsKey("type"))
if (rec != null && rec.dataMap().containsKey("type"))
{
String type = String.valueOf(record.dataMap().get("type"));
String type = String.valueOf(rec.dataMap().get("type"));
int counter = this.typeSlotMap.computeIfAbsent(type, key -> 0);
this.typeSlotMap.put(type, counter + 1);
@ -226,11 +226,11 @@ public class TemplatedPanel extends Panel
// Analyze the template
for (int i = 0; i < 5; i++)
{
ItemTemplateRecord record = this.panelTemplate.content()[0][i];
ItemTemplateRecord rec = this.panelTemplate.content()[0][i];
if (record != null && record.dataMap().containsKey("type"))
if (rec != null && rec.dataMap().containsKey("type"))
{
String type = String.valueOf(record.dataMap().get("type"));
String type = String.valueOf(rec.dataMap().get("type"));
int counter = this.typeSlotMap.computeIfAbsent(type, key -> 0);
this.typeSlotMap.put(type, counter + 1);
@ -289,11 +289,11 @@ public class TemplatedPanel extends Panel
{
for (int k = 0; k < 3; k++)
{
ItemTemplateRecord record = this.panelTemplate.content()[i][k];
ItemTemplateRecord rec = this.panelTemplate.content()[i][k];
if (record != null && record.dataMap().containsKey("type"))
if (rec != null && rec.dataMap().containsKey("type"))
{
String type = String.valueOf(record.dataMap().get("type"));
String type = String.valueOf(rec.dataMap().get("type"));
int counter = this.typeSlotMap.computeIfAbsent(type, key -> 0);
this.typeSlotMap.put(type, counter + 1);
@ -354,41 +354,41 @@ public class TemplatedPanel extends Panel
/**
* This method passes button creation from given record template.
* @param record Template of the button that must be created.
* @param rec Template of the button that must be created.
* @return PanelItem of the template, otherwise null.
*/
@Nullable
private PanelItem makeButton(@Nullable ItemTemplateRecord record)
private PanelItem makeButton(@Nullable ItemTemplateRecord rec)
{
if (record == null)
if (rec == null)
{
// Immediate exit if record is null.
return null;
}
if (record.dataMap().containsKey("type"))
if (rec.dataMap().containsKey("type"))
{
// If dataMap is not null, and it is not empty, then pass button to the object creator function.
return this.makeAddonButton(record);
return this.makeAddonButton(rec);
}
else
{
PanelItemBuilder itemBuilder = new PanelItemBuilder();
if (record.icon() != null)
if (rec.icon() != null)
{
itemBuilder.icon(record.icon().clone());
itemBuilder.icon(rec.icon().clone());
}
if (record.title() != null)
if (rec.title() != null)
{
itemBuilder.name(this.user.getTranslation(record.title()));
itemBuilder.name(this.user.getTranslation(rec.title()));
}
if (record.description() != null)
if (rec.description() != null)
{
itemBuilder.description(this.user.getTranslation(record.description()));
itemBuilder.description(this.user.getTranslation(rec.description()));
}
// If there are generic click handlers that could be added, then this is a place
@ -402,19 +402,19 @@ public class TemplatedPanel extends Panel
/**
* This method passes button to the type creator, if that exists.
* @param record Template of the button that must be created.
* @param rec Template of the button that must be created.
* @return PanelItem of the button created by typeCreator, otherwise null.
*/
@Nullable
private PanelItem makeAddonButton(@NonNull ItemTemplateRecord record)
private PanelItem makeAddonButton(@NonNull ItemTemplateRecord rec)
{
// Get object type.
String type = String.valueOf(record.dataMap().getOrDefault("type", ""));
String type = String.valueOf(rec.dataMap().getOrDefault("type", ""));
if (!this.typeCreators.containsKey(type))
{
// There are no object with a given type.
return this.makeFallBack(record.fallback());
return this.makeFallBack(rec.fallback());
}
BiFunction<ItemTemplateRecord, ItemSlot, PanelItem> buttonBuilder = this.typeCreators.get(type);
@ -426,48 +426,48 @@ public class TemplatedPanel extends Panel
this.typeIndex.put(type, itemSlot.nextItemSlot());
// Try to get next object.
PanelItem item = buttonBuilder.apply(record, itemSlot);
return item == null ? this.makeFallBack(record.fallback()) : item;
PanelItem item = buttonBuilder.apply(rec, itemSlot);
return item == null ? this.makeFallBack(rec.fallback()) : item;
}
/**
* This method creates a fall back button for given record.
* @param record Record which fallback must be created.
* @param rec Record which fallback must be created.
* @return PanelItem if fallback was creates successfully, otherwise null.
*/
@Nullable
private PanelItem makeFallBack(@Nullable ItemTemplateRecord record)
private PanelItem makeFallBack(@Nullable ItemTemplateRecord rec)
{
return record == null ? null : this.makeButton(record.fallback());
return rec == null ? null : this.makeButton(rec.fallback());
}
/**
* This method translates template record into a panel item.
* @param record Record that must be translated.
* @param rec Record that must be translated.
* @return PanelItem that contains all information from the record.
*/
private PanelItem makeTemplate(PanelTemplateRecord.TemplateItem record)
private PanelItem makeTemplate(PanelTemplateRecord.TemplateItem rec)
{
PanelItemBuilder itemBuilder = new PanelItemBuilder();
// Read icon only if it is not null.
if (record.icon() != null)
if (rec.icon() != null)
{
itemBuilder.icon(record.icon().clone());
itemBuilder.icon(rec.icon().clone());
}
// Read title only if it is not null.
if (record.title() != null)
if (rec.title() != null)
{
itemBuilder.name(this.user.getTranslation(record.title()));
itemBuilder.name(this.user.getTranslation(rec.title()));
}
// Read description only if it is not null.
if (record.description() != null)
if (rec.description() != null)
{
itemBuilder.description(this.user.getTranslation(record.description()));
itemBuilder.description(this.user.getTranslation(rec.description()));
}
// Click Handlers are managed by custom addon buttons.

View File

@ -8,7 +8,11 @@ package world.bentobox.bentobox.api.panels.builders;
import java.io.File;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import org.bukkit.World;

View File

@ -19,7 +19,6 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords;
/**
* This Record contains all necessary information about Item Template that can be used to craft panel item.
*

View File

@ -17,7 +17,6 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
/**
* This is template object for the panel reader. It contains data that can exist in the panel.
* PanelBuilder will use this to build panel.
@ -98,10 +97,9 @@ public record PanelTemplateRecord(Panel.Type type,
if (this == obj) {
return true;
}
if (!(obj instanceof PanelTemplateRecord)) {
if (!(obj instanceof PanelTemplateRecord other)) {
return false;
}
PanelTemplateRecord other = (PanelTemplateRecord) obj;
return Objects.equals(background, other.background) && Objects.equals(border, other.border)
&& Arrays.deepEquals(content, other.content) && Arrays.equals(forcedRows, other.forcedRows)
&& Objects.equals(title, other.title) && type == other.type;

View File

@ -6,6 +6,12 @@
package world.bentobox.bentobox.api.panels.reader;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
@ -14,12 +20,6 @@ import org.bukkit.event.inventory.ClickType;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.base.Enums;
import world.bentobox.bentobox.api.panels.Panel;
@ -49,6 +49,14 @@ public class TemplateReader
private static final String TYPE = "type";
/**
* Utility classes, which are collections of static members, are not meant to be instantiated.
* Even abstract utility classes, which can be extended, should not have public constructors.
* Java adds an implicit public constructor to every class which does not define at least one explicitly.
* Hence, at least one non-public constructor should be defined.
*/
private TemplateReader() {}
/**
* Read template panel panel template record.
*
@ -95,7 +103,7 @@ public class TemplateReader
return TemplateReader.loadedPanels.get(panelKey);
}
PanelTemplateRecord record;
PanelTemplateRecord rec;
try
{
@ -103,16 +111,16 @@ public class TemplateReader
YamlConfiguration config = new YamlConfiguration();
config.load(file);
// Read panel
record = readPanelTemplate(config.getConfigurationSection(panelName));
rec = readPanelTemplate(config.getConfigurationSection(panelName));
// Put panel into memory
TemplateReader.loadedPanels.put(panelKey, record);
TemplateReader.loadedPanels.put(panelKey, rec);
}
catch (IOException | InvalidConfigurationException e)
{
record = null;
rec = null;
}
return record;
return rec;
}

View File

@ -6,6 +6,7 @@ import java.util.Map;
import org.bukkit.entity.Player;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import world.bentobox.bentobox.api.placeholders.PlaceholderReplacer;
@ -42,9 +43,9 @@ abstract class BasicPlaceholderExpansion extends PlaceholderExpansion {
}
@Override
public String onPlaceholderRequest(Player p, @NonNull String placeholder) {
if (placeholders.containsKey(placeholder) && p != null) {
return placeholders.get(placeholder).onReplace(User.getInstance(p));
public String onPlaceholderRequest(@Nullable Player p, @NonNull String placeholder) {
if (placeholders.containsKey(placeholder)) {
return placeholders.get(placeholder).onReplace(p != null ? User.getInstance(p) : null);
}
return null;
}

View File

@ -1,5 +1,7 @@
package world.bentobox.bentobox.api.user;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -8,7 +10,6 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang.math.NumberUtils;
import org.bukkit.Bukkit;
@ -17,6 +18,7 @@ import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.Particle;
import org.bukkit.Particle.DustTransition;
import org.bukkit.Vibration;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
@ -54,6 +56,26 @@ public class User implements MetaDataAble {
private static final Map<UUID, User> users = new HashMap<>();
// Used for particle validation
private static final Map<Particle, Class<?>> VALIDATION_CHECK;
static {
Map<Particle, Class<?>> v = new EnumMap<>(Particle.class);
v.put(Particle.REDSTONE, Particle.DustOptions.class);
v.put(Particle.ITEM_CRACK, ItemStack.class);
v.put(Particle.BLOCK_CRACK, BlockData.class);
v.put(Particle.BLOCK_DUST, BlockData.class);
v.put(Particle.FALLING_DUST, BlockData.class);
v.put(Particle.BLOCK_MARKER, BlockData.class);
v.put(Particle.DUST_COLOR_TRANSITION, DustTransition.class);
v.put(Particle.VIBRATION, Vibration.class);
v.put(Particle.SCULK_CHARGE, Float.class);
v.put(Particle.SHRIEK, Integer.class);
v.put(Particle.LEGACY_BLOCK_CRACK, BlockData.class);
v.put(Particle.LEGACY_BLOCK_DUST, BlockData.class);
v.put(Particle.LEGACY_FALLING_DUST, BlockData.class);
VALIDATION_CHECK = Collections.unmodifiableMap(v);
}
/**
* Clears all users from the user list
*/
@ -89,7 +111,8 @@ public class User implements MetaDataAble {
}
/**
* Gets an instance of User from a UUID.
* Gets an instance of User from a UUID. This will always return a user object.
* If the player is offline then the getPlayer value will be null.
* @param uuid - UUID
* @return user - user
*/
@ -98,7 +121,7 @@ public class User implements MetaDataAble {
if (users.containsKey(uuid)) {
return users.get(uuid);
}
// Return player, or null if they are not online
// Return a user instance
return new User(uuid);
}
@ -317,8 +340,6 @@ public class User implements MetaDataAble {
// If requester is console, then return the default value
if (!isPlayer()) return defaultValue;
int value = 0;
// If there is a dot at the end of the permissionPrefix, remove it
if (permissionPrefix.endsWith(".")) {
permissionPrefix = permissionPrefix.substring(0, permissionPrefix.length()-1);
@ -330,10 +351,16 @@ public class User implements MetaDataAble {
.filter(PermissionAttachmentInfo::getValue) // Must be a positive permission, not a negative one
.map(PermissionAttachmentInfo::getPermission)
.filter(permission -> permission.startsWith(permPrefix))
.collect(Collectors.toList());
.toList();
if (permissions.isEmpty()) return defaultValue;
return iteratePerms(permissions, permPrefix, defaultValue);
}
private int iteratePerms(List<String> permissions, String permPrefix, int defaultValue) {
int value = 0;
for (String permission : permissions) {
if (permission.contains(permPrefix + "*")) {
// 'Star' permission
@ -403,21 +430,27 @@ public class User implements MetaDataAble {
}
private String translate(String addonPrefix, String reference, String[] variables) {
// Try to get the translation for this specific addon
String translation = plugin.getLocalesManager().get(this, addonPrefix + reference);
if (translation == null) {
// No luck, try to get the generic translation
translation = plugin.getLocalesManager().get(this, reference);
if (translation == null) {
// If no translation has been found, return the reference for debug purposes.
return reference;
// Nothing found. Replace vars (probably will do nothing) and return
return replaceVars(reference, variables);
}
}
// If this is a prefix, just gather and return the translation
if (reference.startsWith("prefixes.")) {
return translation;
} else {
if (!reference.startsWith("prefixes.")) {
// Replace the prefixes
return replacePrefixes(translation, variables);
}
return translation;
}
private String replacePrefixes(String translation, String[] variables) {
for (String prefix : plugin.getLocalesManager().getAvailablePrefixes(this)) {
String prefixTranslation = getTranslation("prefixes." + prefix);
// Replace the [gamemode] text variable
@ -440,9 +473,25 @@ public class User implements MetaDataAble {
if (player != null) {
translation = plugin.getPlaceholdersManager().replacePlaceholders(player, translation);
}
return translation;
}
private String replaceVars(String reference, String[] variables) {
// Then replace variables
if (variables.length > 1) {
for (int i = 0; i < variables.length; i += 2) {
reference = reference.replace(variables[i], variables[i + 1]);
}
}
// Then replace Placeholders, this will only work if this is a player
if (player != null) {
reference = plugin.getPlaceholdersManager().replacePlaceholders(player, reference);
}
// If no translation has been found, return the reference for debug purposes.
return reference;
}
/**
@ -600,72 +649,18 @@ public class User implements MetaDataAble {
* @param y Y coordinate of the particle to display.
* @param z Z coordinate of the particle to display.
*/
public void spawnParticle(Particle particle, Object dustOptions, double x, double y, double z)
public void spawnParticle(Particle particle, @Nullable Object dustOptions, double x, double y, double z)
{
// Improve particle validation.
switch (particle)
{
case REDSTONE ->
{
if (!(dustOptions instanceof Particle.DustOptions))
{
throw new IllegalArgumentException("A non-null Particle.DustOptions must be provided when using Particle.REDSTONE as particle.");
}
}
case ITEM_CRACK ->
{
if (!(dustOptions instanceof ItemStack))
{
throw new IllegalArgumentException("A non-null ItemStack must be provided when using Particle.ITEM_CRACK as particle.");
}
}
case BLOCK_CRACK, BLOCK_DUST, FALLING_DUST, BLOCK_MARKER ->
{
if (!(dustOptions instanceof BlockData))
{
throw new IllegalArgumentException("A non-null BlockData must be provided when using Particle." + particle + " as particle.");
}
}
case DUST_COLOR_TRANSITION ->
{
if (!(dustOptions instanceof Particle.DustTransition))
{
throw new IllegalArgumentException("A non-null Particle.DustTransition must be provided when using Particle.DUST_COLOR_TRANSITION as particle.");
}
}
case VIBRATION ->
{
if (!(dustOptions instanceof Vibration))
{
throw new IllegalArgumentException("A non-null Vibration must be provided when using Particle.VIBRATION as particle.");
}
}
case SCULK_CHARGE ->
{
if (!(dustOptions instanceof Float))
{
throw new IllegalArgumentException("A non-null Float must be provided when using Particle.SCULK_CHARGE as particle.");
}
}
case SHRIEK ->
{
if (!(dustOptions instanceof Integer))
{
throw new IllegalArgumentException("A non-null Integer must be provided when using Particle.SHRIEK as particle.");
}
}
case LEGACY_BLOCK_CRACK, LEGACY_BLOCK_DUST, LEGACY_FALLING_DUST ->
{
if (!(dustOptions instanceof BlockData))
{
throw new IllegalArgumentException("A non-null MaterialData must be provided when using Particle." + particle + " as particle.");
}
}
Class<?> expectedClass = VALIDATION_CHECK.get(particle);
if (expectedClass == null) throw new IllegalArgumentException("Unexpected value: " + particle);
if (!(expectedClass.isInstance(dustOptions))) {
throw new IllegalArgumentException("A non-null " + expectedClass.getSimpleName() + " must be provided when using Particle." + particle + " as particle.");
}
// Check if this particle is beyond the viewing distance of the server
if (this.player != null &&
this.player.getLocation().toVector().distanceSquared(new Vector(x, y, z)) <
if (this.player != null
&& this.player.getLocation().toVector().distanceSquared(new Vector(x, y, z)) <
(Bukkit.getServer().getViewDistance() * 256 * Bukkit.getServer().getViewDistance()))
{
if (particle.equals(Particle.REDSTONE))
@ -678,6 +673,7 @@ public class User implements MetaDataAble {
}
else
{
// This will never be called unless the value in VALIDATION_CHECK is null in the future
player.spawnParticle(particle, x, y, z, 1);
}
}

View File

@ -2,7 +2,6 @@ package world.bentobox.bentobox.blueprints;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.bukkit.Material;

View File

@ -8,7 +8,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@ -138,7 +137,7 @@ public class BlueprintClipboard {
.filter(e -> new Vector(Math.rint(e.getLocation().getX()),
Math.rint(e.getLocation().getY()),
Math.rint(e.getLocation().getZ())).equals(v))
.collect(Collectors.toList());
.toList();
if (copyBlock(v.toLocation(world), copyAir, copyBiome, ents)) {
count++;
}
@ -288,19 +287,17 @@ public class BlueprintClipboard {
if (entity instanceof Villager villager) {
setVillager(villager, bpe);
}
if (entity instanceof Colorable c) {
if (c.getColor() != null) {
if (entity instanceof Colorable c && c.getColor() != null) {
bpe.setColor(c.getColor());
}
if (entity instanceof Tameable tameable) {
bpe.setTamed(tameable.isTamed());
}
if (entity instanceof Tameable) {
bpe.setTamed(((Tameable)entity).isTamed());
}
if (entity instanceof ChestedHorse) {
bpe.setChest(((ChestedHorse)entity).isCarryingChest());
if (entity instanceof ChestedHorse chestedHorse) {
bpe.setChest(chestedHorse.isCarryingChest());
}
// Only set if child. Most animals are adults
if (entity instanceof Ageable && !((Ageable)entity).isAdult()) {
if (entity instanceof Ageable ageable && !ageable.isAdult()) {
bpe.setAdult(false);
}
if (entity instanceof AbstractHorse horse) {

View File

@ -1,5 +1,17 @@
package world.bentobox.bentobox.blueprints;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
@ -7,6 +19,7 @@ import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
@ -16,12 +29,6 @@ import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.nms.PasteHandler;
import world.bentobox.bentobox.util.Util;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
/**
* This class pastes the clipboard it is given
* @author tastybento
@ -77,10 +84,10 @@ public class BlueprintPaster {
private final Island island;
/**
* Paste a clipboard to a location and run task
* Paste a clipboard to a location. Run {@link #paste()} to paste
* @param plugin - BentoBox
* @param clipboard - clipboard to paste
* @param location - location to paste to
* @param location - location to which to paste
*/
public BlueprintPaster(@NonNull BentoBox plugin, @NonNull BlueprintClipboard clipboard, @NonNull Location location) {
this.plugin = plugin;
@ -90,9 +97,6 @@ public class BlueprintPaster {
this.location = location;
this.world = location.getWorld();
this.island = null;
// Paste
paste();
}
/**
@ -151,20 +155,71 @@ public class BlueprintPaster {
final int pasteSpeed = plugin.getSettings().getPasteSpeed();
long timer = System.currentTimeMillis();
int count = 0;
if (pasteState.equals(PasteState.CHUNK_LOAD)) {
pasteState = PasteState.CHUNK_LOADING;
// Load chunk
currentTask = Util.getChunkAtAsync(location).thenRun(() -> {
pasteState = PasteState.BLOCKS;
long duration = System.currentTimeMillis() - timer;
if (duration > chunkLoadTime) {
chunkLoadTime = duration;
}
});
loadChunk();
}
else if (pasteState.equals(PasteState.BLOCKS) || pasteState.equals(PasteState.ATTACHMENTS)) {
pasteBlocks(bits, count, owner, pasteSpeed);
}
else if (pasteState.equals(PasteState.ENTITIES)) {
pasteEntities(bits, count, owner, pasteSpeed);
}
else if (pasteState.equals(PasteState.DONE)) {
// All done. Cancel task
cancelTask(result);
} else if (pasteState.equals(PasteState.CANCEL)) {
// This state makes sure the follow-on task only ever runs once
pastingTask.cancel();
result.complete(true);
}
}
private void cancelTask(CompletableFuture<Boolean> result) {
// Set pos1 and 2 if this was a clipboard paste
if (island == null && clipboard != null) {
clipboard.setPos1(pos1);
clipboard.setPos2(pos2);
}
pasteState = PasteState.CANCEL;
result.complete(true);
}
private void pasteEntities(Bits bits, int count, Optional<User> owner, int pasteSpeed) {
if (bits.it3().hasNext()) {
Map<Location, List<BlueprintEntity>> entityMap = new HashMap<>();
// Paste entities
while (count < pasteSpeed) {
if (!bits.it3().hasNext()) {
break;
}
Entry<Vector, List<BlueprintEntity>> entry = bits.it3().next();
int x = location.getBlockX() + entry.getKey().getBlockX();
int y = location.getBlockY() + entry.getKey().getBlockY();
int z = location.getBlockZ() + entry.getKey().getBlockZ();
Location center = new Location(world, x, y, z).add(new Vector(0.5, 0.5, 0.5));
List<BlueprintEntity> entities = entry.getValue();
entityMap.put(center, entities);
count++;
}
if (!entityMap.isEmpty()) {
currentTask = paster.pasteEntities(island, world, entityMap);
}
} else {
pasteState = PasteState.DONE;
String dimensionType = switch (location.getWorld().getEnvironment()) {
case NETHER -> owner.map(user -> user.getTranslation("general.worlds.nether")).orElse("");
case THE_END -> owner.map(user -> user.getTranslation("general.worlds.the-end")).orElse("");
default -> owner.map(user -> user.getTranslation("general.worlds.overworld")).orElse("");
};
owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.dimension-done", "[world]", dimensionType));
}
}
private void pasteBlocks(Bits bits, int count, Optional<User> owner, int pasteSpeed) {
Iterator<Entry<Vector, BlueprintBlock>> it = pasteState.equals(PasteState.BLOCKS) ? bits.it : bits.it2;
if (it.hasNext()) {
Map<Location, BlueprintBlock> blockMap = new HashMap<>();
@ -198,53 +253,21 @@ public class BlueprintPaster {
}
}
}
}
else if (pasteState.equals(PasteState.ENTITIES)) {
if (bits.it3().hasNext()) {
Map<Location, List<BlueprintEntity>> entityMap = new HashMap<>();
// Paste entities
while (count < pasteSpeed) {
if (!bits.it3().hasNext()) {
break;
}
Entry<Vector, List<BlueprintEntity>> entry = bits.it3().next();
int x = location.getBlockX() + entry.getKey().getBlockX();
int y = location.getBlockY() + entry.getKey().getBlockY();
int z = location.getBlockZ() + entry.getKey().getBlockZ();
Location center = new Location(world, x, y, z).add(new Vector(0.5, 0.5, 0.5));
List<BlueprintEntity> entities = entry.getValue();
entityMap.put(center, entities);
count++;
}
if (!entityMap.isEmpty()) {
currentTask = paster.pasteEntities(island, world, entityMap);
}
} else {
pasteState = PasteState.DONE;
String world = switch (location.getWorld().getEnvironment()) {
case NETHER -> owner.map(user -> user.getTranslation("general.worlds.nether")).orElse("");
case THE_END -> owner.map(user -> user.getTranslation("general.worlds.the-end")).orElse("");
default -> owner.map(user -> user.getTranslation("general.worlds.overworld")).orElse("");
};
}
owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.dimension-done", "[world]", world));
}
}
else if (pasteState.equals(PasteState.DONE)) {
// All done. Cancel task
// Set pos1 and 2 if this was a clipboard paste
if (island == null && clipboard != null) {
clipboard.setPos1(pos1);
clipboard.setPos2(pos2);
}
pasteState = PasteState.CANCEL;
result.complete(true);
} else if (pasteState.equals(PasteState.CANCEL)) {
// This state makes sure the follow-on task only ever runs once
pastingTask.cancel();
result.complete(true);
private void loadChunk() {
long timer = System.currentTimeMillis();
pasteState = PasteState.CHUNK_LOADING;
// Load chunk
currentTask = Util.getChunkAtAsync(location).thenRun(() -> {
pasteState = PasteState.BLOCKS;
long duration = System.currentTimeMillis() - timer;
if (duration > chunkLoadTime) {
chunkLoadTime = duration;
}
});
}
private void tellOwner(User user, int blocksSize, int attachedSize, int entitiesSize, int pasteSpeed) {

View File

@ -8,7 +8,6 @@ import org.bukkit.entity.Player;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.Blueprint;

View File

@ -3,7 +3,6 @@ package world.bentobox.bentobox.blueprints.dataobjects;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.bukkit.Material;

View File

@ -99,12 +99,11 @@ public class Database<T> {
}
/**
* Save object. Saving may be done async or sync, depending on the underlying database.
* Save object. Saving is done async. Same as {@link #saveObjectAsync(Object)}, which is recommended.
* @param instance to save
* @return true - always.
* @deprecated As of 1.13.0. Use {@link #saveObjectAsync(Object)}.
* @since 1.13.0
*/
@Deprecated
public boolean saveObject(T instance) {
saveObjectAsync(instance).thenAccept(r -> {
if (Boolean.FALSE.equals(r)) logger.severe(() -> "Could not save object to database!");

View File

@ -1,5 +1,13 @@
package world.bentobox.bentobox.database;
import java.util.Collections;
import java.util.Map;
/**
* The type Database connection settings.
*/
public class DatabaseConnectionSettingsImpl {
private String host;
private int port;
@ -14,6 +22,18 @@ public class DatabaseConnectionSettingsImpl {
*/
private boolean useSSL;
/**
* Number of max connections in pool.
* @since 1.21.0
*/
private int maxConnections;
/**
* Map of extra properties.
* @since 1.21.0
*/
private Map<String, String> extraProperties;
/**
* Hosts database settings
* @param host - database host
@ -21,16 +41,78 @@ public class DatabaseConnectionSettingsImpl {
* @param databaseName - database name
* @param username - username
* @param password - password
* @param useSSL - whether to use SSL or not
* @param maxConnections - max number of connections
* @param extraProperties Map with extra properties.
*/
public DatabaseConnectionSettingsImpl(String host, int port, String databaseName, String username, String password, boolean useSSL) {
this.host = host;
this.port = port;
this.databaseName = databaseName;
this.username = username;
this.password = password;
this.useSSL = useSSL;
public record DatabaseSettings(String host,
int port,
String databaseName,
String username,
String password,
boolean useSSL,
int maxConnections,
Map<String, String> extraProperties) {}
/**
* Hosts database settings
* @param settings - database settings see {@link DatabaseSettings}
*/
public DatabaseConnectionSettingsImpl(DatabaseSettings settings)
{
this.host = settings.host;
this.port = settings.port;
this.databaseName = settings.databaseName;
this.username = settings.username;
this.password = settings.password;
this.useSSL = settings.useSSL;
this.maxConnections = settings.maxConnections;
this.extraProperties = settings.extraProperties;
}
/**
* Hosts database settings
* @param host - database host
* @param port - port
* @param databaseName - database name
* @param username - username
* @param password - password
* @param useSSL - ssl usage.
* @param maxConnections - number of maximal connections in pool.
*/
public DatabaseConnectionSettingsImpl(String host,
int port,
String databaseName,
String username,
String password,
boolean useSSL,
int maxConnections)
{
this(new DatabaseSettings(host, port, databaseName, username, password, useSSL, maxConnections, Collections.emptyMap()));
}
/**
* Hosts database settings
* @param host - database host
* @param port - port
* @param databaseName - database name
* @param username - username
* @param password - password
* @param useSSL - ssl usage.
*/
public DatabaseConnectionSettingsImpl(String host,
int port,
String databaseName,
String username,
String password,
boolean useSSL)
{
this(new DatabaseSettings(host, port, databaseName, username, password, useSSL, 0, Collections.emptyMap()));
}
/**
* @return the host
*/
@ -117,4 +199,48 @@ public class DatabaseConnectionSettingsImpl {
public void setUseSSL(boolean useSSL) {
this.useSSL = useSSL;
}
/**
* Gets max connections.
*
* @return the max connections
*/
public int getMaxConnections()
{
return this.maxConnections;
}
/**
* Sets max connections.
*
* @param maxConnections the max connections
*/
public void setMaxConnections(int maxConnections)
{
this.maxConnections = maxConnections;
}
/**
* Gets extra properties.
*
* @return the extra properties
*/
public Map<String, String> getExtraProperties()
{
return extraProperties;
}
/**
* Sets extra properties.
*
* @param extraProperties the extra properties
*/
public void setExtraProperties(Map<String, String> extraProperties)
{
this.extraProperties = extraProperties;
}
}

View File

@ -45,10 +45,5 @@ public interface DatabaseConnector {
* @return true if it exists
*/
boolean uniqueIdExists(String tableName, String key);
}

View File

@ -26,7 +26,6 @@ import world.bentobox.bentobox.database.json.adapters.LocationTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.PotionEffectTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.VectorTypeAdapter;
import world.bentobox.bentobox.database.json.adapters.WorldTypeAdapter;
import world.bentobox.bentobox.versions.ServerCompatibility;
/**

View File

@ -21,7 +21,7 @@ public final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
/**
* Bimap to store name <-> enum references
* Bimap to store name,enum pair references
*/
private final BiMap<String, T> enumMap = HashBiMap.create();

View File

@ -24,8 +24,9 @@ public class LocationTypeAdapter extends TypeAdapter<Location> {
out.value(location.getX());
out.value(location.getY());
out.value(location.getZ());
out.value(location.getYaw());
out.value(location.getPitch());
// This is required for 1.19-1.19.2 compatibility.
out.value((double) location.getYaw());
out.value((double) location.getPitch());
out.endArray();
}

View File

@ -5,9 +5,6 @@ import com.google.gson.annotations.Expose;
/**
* Record for bonus ranges
* @author tastybento
* @param id an id to identify this bonus
* @param range the additional bonus range
* @param message the reference key to a locale message related to this bonus. May be blank.
*/
public class BonusRangeRecord {
@Expose
@ -17,9 +14,9 @@ public class BonusRangeRecord {
@Expose
private String message;
/**
* @param uniqueId
* @param range
* @param message
* @param uniqueId an id to identify this bonus
* @param range the additional bonus range
* @param message the reference key to a locale message related to this bonus. May be blank.
*/
public BonusRangeRecord(String uniqueId, int range, String message) {
this.uniqueId = uniqueId;

View File

@ -16,9 +16,9 @@ import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.GameMode;
import org.bukkit.World.Environment;
import org.bukkit.entity.Player;
import org.bukkit.util.BoundingBox;

View File

@ -1,11 +1,12 @@
package world.bentobox.bentobox.database.objects.adapters;
import org.bukkit.configuration.MemorySection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.bukkit.configuration.MemorySection;
/**
* This Serializer migrates Map of String, Boolean to Map of String, Integer in serialization process.
@ -35,7 +36,7 @@ public class FlagBooleanSerializer implements AdapterInterface<Map<String, Integ
{
for (Entry<String, Boolean> en : ((Map<String, Boolean>) object).entrySet())
{
result.put(en.getKey(), en.getValue() ? 0 : -1);
result.put(en.getKey(), Boolean.TRUE.equals(en.getValue()) ? 0 : -1);
}
}

View File

@ -29,7 +29,7 @@ public class FlagSerializer2 implements AdapterInterface<Map<Flag, Integer>, Map
}
} else {
for (Entry<String, Boolean> en : ((Map<String, Boolean>)object).entrySet()) {
BentoBox.getInstance().getFlagsManager().getFlag(en.getKey()).ifPresent(flag -> result.put(flag, en.getValue() ? 0 : -1));
BentoBox.getInstance().getFlagsManager().getFlag(en.getKey()).ifPresent(flag -> result.put(flag, Boolean.TRUE.equals(en.getValue()) ? 0 : -1));
}
}
return result;

View File

@ -9,34 +9,44 @@ import world.bentobox.bentobox.database.objects.Table;
* @author tastybento
*
*/
public class SQLConfiguration {
public class SQLConfiguration
{
private String loadObjectSQL;
private String saveObjectSQL;
private String deleteObjectSQL;
private String objectExistsSQL;
private String schemaSQL;
private String loadObjectsSQL;
private String renameTableSQL;
private final String tableName;
private final boolean renameRequired;
private final String oldTableName;
public <T> SQLConfiguration(BentoBox plugin, Class<T> type) {
public <T> SQLConfiguration(BentoBox plugin, Class<T> type)
{
// Set the table name
oldTableName = plugin.getSettings().getDatabasePrefix() + type.getCanonicalName();
this.oldTableName = plugin.getSettings().getDatabasePrefix() + type.getCanonicalName();
this.tableName = plugin.getSettings().getDatabasePrefix() +
(type.getAnnotation(Table.class) == null ?
type.getCanonicalName()
: type.getAnnotation(Table.class).name());
(type.getAnnotation(Table.class) == null ? type.getCanonicalName() : type.getAnnotation(Table.class).name());
// Only rename if there is a specific Table annotation
renameRequired = !tableName.equals(oldTableName);
schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) )");
loadObjects("SELECT `json` FROM `[tableName]`");
loadObject("SELECT `json` FROM `[tableName]` WHERE uniqueId = ? LIMIT 1");
saveObject("INSERT INTO `[tableName]` (json) VALUES (?) ON DUPLICATE KEY UPDATE json = ?");
deleteObject("DELETE FROM `[tableName]` WHERE uniqueId = ?");
objectExists("SELECT IF ( EXISTS( SELECT * FROM `[tableName]` WHERE `uniqueId` = ?), 1, 0)");
renameTable("SELECT Count(*) INTO @exists " +
this.renameRequired = !this.tableName.equals(this.oldTableName);
this.schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) )");
this.loadObjects("SELECT `json` FROM `[tableName]`");
this.loadObject("SELECT `json` FROM `[tableName]` WHERE uniqueId = ? LIMIT 1");
this.saveObject("INSERT INTO `[tableName]` (json) VALUES (?) ON DUPLICATE KEY UPDATE json = ?");
this.deleteObject("DELETE FROM `[tableName]` WHERE uniqueId = ?");
this.objectExists("SELECT IF ( EXISTS( SELECT * FROM `[tableName]` WHERE `uniqueId` = ?), 1, 0)");
this.renameTable("SELECT Count(*) INTO @exists " +
"FROM information_schema.tables " +
"WHERE table_schema = '" + plugin.getSettings().getDatabaseName() + "' " +
"AND table_type = 'BASE TABLE' " +
@ -46,48 +56,66 @@ public class SQLConfiguration {
"EXECUTE stmt;");
}
private final String TABLE_NAME = "\\[tableName]";
private static final String TABLE_NAME = "\\[tableName]";
/**
* By default, use quotes around the unique ID in the SQL statement
*/
private boolean useQuotes = true;
public SQLConfiguration loadObject(String string) {
public SQLConfiguration loadObject(String string)
{
this.loadObjectSQL = string.replaceFirst(TABLE_NAME, tableName);
return this;
}
public SQLConfiguration saveObject(String string) {
public SQLConfiguration saveObject(String string)
{
this.saveObjectSQL = string.replaceFirst(TABLE_NAME, tableName);
return this;
}
public SQLConfiguration deleteObject(String string) {
public SQLConfiguration deleteObject(String string)
{
this.deleteObjectSQL = string.replaceFirst(TABLE_NAME, tableName);
return this;
}
public SQLConfiguration objectExists(String string) {
public SQLConfiguration objectExists(String string)
{
this.objectExistsSQL = string.replaceFirst(TABLE_NAME, tableName);
return this;
}
public SQLConfiguration schema(String string) {
public SQLConfiguration schema(String string)
{
this.schemaSQL = string.replaceFirst(TABLE_NAME, tableName);
return this;
}
public SQLConfiguration loadObjects(String string) {
public SQLConfiguration loadObjects(String string)
{
this.loadObjectsSQL = string.replaceFirst(TABLE_NAME, tableName);
return this;
}
public SQLConfiguration renameTable(String string) {
public SQLConfiguration renameTable(String string)
{
this.renameTableSQL = string.replace(TABLE_NAME, tableName).replace("\\[oldTableName\\]", oldTableName);
return this;
}
public SQLConfiguration setUseQuotes(boolean b) {
public SQLConfiguration setUseQuotes(boolean b)
{
this.useQuotes = b;
return this;
}
@ -96,71 +124,95 @@ public class SQLConfiguration {
/**
* @return the loadObjectSQL
*/
public String getLoadObjectSQL() {
public String getLoadObjectSQL()
{
return loadObjectSQL;
}
/**
* @return the saveObjectSQL
*/
public String getSaveObjectSQL() {
public String getSaveObjectSQL()
{
return saveObjectSQL;
}
/**
* @return the deleteObjectSQL
*/
public String getDeleteObjectSQL() {
public String getDeleteObjectSQL()
{
return deleteObjectSQL;
}
/**
* @return the objectExistsSQL
*/
public String getObjectExistsSQL() {
public String getObjectExistsSQL()
{
return objectExistsSQL;
}
/**
* @return the schemaSQL
*/
public String getSchemaSQL() {
public String getSchemaSQL()
{
return schemaSQL;
}
/**
* @return the loadItSQL
*/
public String getLoadObjectsSQL() {
public String getLoadObjectsSQL()
{
return loadObjectsSQL;
}
/**
* @return the renameTableSQL
*/
public String getRenameTableSQL() {
public String getRenameTableSQL()
{
return renameTableSQL;
}
/**
* @return the tableName
*/
public String getTableName() {
public String getTableName()
{
return tableName;
}
/**
* @return the oldName
*/
public String getOldTableName() {
public String getOldTableName()
{
return oldTableName;
}
public boolean renameRequired() {
public boolean renameRequired()
{
return renameRequired;
}
/**
* @return the useQuotes
*/
public boolean isUseQuotes() {
public boolean isUseQuotes()
{
return useQuotes;
}
}

View File

@ -1,7 +1,6 @@
package world.bentobox.bentobox.database.sql;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;
@ -9,64 +8,136 @@ import java.util.Set;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.DatabaseConnector;
public abstract class SQLDatabaseConnector implements DatabaseConnector {
/**
* Generic SQL database connector.
*/
public abstract class SQLDatabaseConnector implements DatabaseConnector
{
/**
* The connection url string for the sql database.
*/
protected String connectionUrl;
private final DatabaseConnectionSettingsImpl dbSettings;
protected static Connection connection = null;
/**
* The database connection settings.
*/
protected final DatabaseConnectionSettingsImpl dbSettings;
/**
* Hikari Data Source that creates all connections.
*/
protected static HikariDataSource dataSource;
/**
* Type of objects stored in database.
*/
protected static Set<Class<?>> types = new HashSet<>();
protected SQLDatabaseConnector(DatabaseConnectionSettingsImpl dbSettings, String connectionUrl) {
/**
* Default connector constructor.
* @param dbSettings Settings of the database.
* @param connectionUrl Connection url for the database.
*/
protected SQLDatabaseConnector(DatabaseConnectionSettingsImpl dbSettings, String connectionUrl)
{
this.dbSettings = dbSettings;
this.connectionUrl = connectionUrl;
}
/**
* Returns connection url of database.
* @return Database connection url.
*/
@Override
public String getConnectionUrl() {
public String getConnectionUrl()
{
return connectionUrl;
}
/**
* {@inheritDoc}
*/
@Override
@NonNull
public String getUniqueId(String tableName) {
public String getUniqueId(String tableName)
{
// Not used
return "";
}
/**
* {@inheritDoc}
*/
@Override
public boolean uniqueIdExists(String tableName, String key) {
public boolean uniqueIdExists(String tableName, String key)
{
// Not used
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void closeConnection(Class<?> type) {
public void closeConnection(Class<?> type)
{
types.remove(type);
if (types.isEmpty() && connection != null) {
try {
connection.close();
if (types.isEmpty())
{
dataSource.close();
Bukkit.getLogger().info("Closed database connection");
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not close database connection");
}
}
}
/**
* This method creates config that is used to create HikariDataSource.
* @return HikariConfig object.
*/
public abstract HikariConfig createConfig();
/**
* {@inheritDoc}
*/
@Override
public Object createConnection(Class<?> type) {
public Object createConnection(Class<?> type)
{
types.add(type);
// Only make one connection to the database
if (connection == null) {
try {
connection = DriverManager.getConnection(connectionUrl, dbSettings.getUsername(), dbSettings.getPassword());
} catch (SQLException e) {
if (dataSource == null)
{
try
{
dataSource = new HikariDataSource(this.createConfig());
// Test connection
try (Connection connection = dataSource.getConnection())
{
connection.isValid(5 * 1000);
}
}
catch (SQLException e)
{
Bukkit.getLogger().severe("Could not connect to the database! " + e.getMessage());
dataSource = null;
}
}
return connection;
}
return dataSource;
}
}

View File

@ -11,6 +11,8 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import javax.sql.DataSource;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
@ -31,246 +33,413 @@ import world.bentobox.bentobox.database.objects.DataObject;
*
* @param <T>
*/
public class SQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
public class SQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T>
{
protected static final String COULD_NOT_LOAD_OBJECTS = "Could not load objects ";
protected static final String COULD_NOT_LOAD_OBJECT = "Could not load object ";
/**
* Connection to the database
* DataSource of database
*/
private Connection connection;
protected DataSource dataSource;
/**
* SQL configuration
*/
private SQLConfiguration sqlConfig;
/**
* Handles the connection to the database and creation of the initial database schema (tables) for
* the class that will be stored.
* @param plugin - plugin object
* @param type - the type of class to be stored in the database. Must inherit DataObject
* @param dbConnecter - authentication details for the database
* @param databaseConnector - authentication details for the database
* @param sqlConfiguration - SQL configuration
*/
protected SQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector dbConnecter, SQLConfiguration sqlConfiguration) {
super(plugin, type, dbConnecter);
protected SQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector, SQLConfiguration sqlConfiguration)
{
super(plugin, type, databaseConnector);
this.sqlConfig = sqlConfiguration;
if (setConnection((Connection)databaseConnector.createConnection(type))) {
if (this.setDataSource((DataSource) this.databaseConnector.createConnection(type)))
{
// Check if the table exists in the database and if not, create it
createSchema();
this.createSchema();
}
}
/**
* @return the sqlConfig
*/
public SQLConfiguration getSqlConfig() {
public SQLConfiguration getSqlConfig()
{
return sqlConfig;
}
/**
* @param sqlConfig the sqlConfig to set
*/
public void setSqlConfig(SQLConfiguration sqlConfig) {
public void setSqlConfig(SQLConfiguration sqlConfig)
{
this.sqlConfig = sqlConfig;
}
/**
* Creates the table in the database if it doesn't exist already
*/
protected void createSchema() {
if (sqlConfig.renameRequired()) {
protected void createSchema()
{
if (this.sqlConfig.renameRequired())
{
// Transition from the old table name
String sql = sqlConfig.getRenameTableSQL().replace("[oldTableName]", sqlConfig.getOldTableName()).replace("[tableName]", sqlConfig.getTableName());
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.execute();
} catch (SQLException e) {
plugin.logError("Could not rename " + sqlConfig.getOldTableName() + " for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
String sql = this.sqlConfig.getRenameTableSQL().
replace("[oldTableName]", this.sqlConfig.getOldTableName()).
replace("[tableName]", this.sqlConfig.getTableName());
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql))
{
preparedStatement.execute();
}
}
// Prepare and execute the database statements
try (PreparedStatement pstmt = connection.prepareStatement(sqlConfig.getSchemaSQL())) {
pstmt.execute();
} catch (SQLException e) {
plugin.logError("Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
catch (SQLException e)
{
this.plugin.logError("Could not rename " + this.sqlConfig.getOldTableName() + " for data object " +
this.dataObject.getCanonicalName() + " " + e.getMessage());
}
}
@Override
public List<T> loadObjects() {
try (Statement preparedStatement = connection.createStatement()) {
return loadIt(preparedStatement);
} catch (SQLException e) {
plugin.logError(COULD_NOT_LOAD_OBJECTS + e.getMessage());
// Prepare and execute the database statements
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(this.sqlConfig.getSchemaSQL()))
{
preparedStatement.execute();
}
catch (SQLException e)
{
this.plugin.logError("Problem trying to create schema for data object " +
this.dataObject.getCanonicalName() + " " + e.getMessage());
}
}
/**
* {@inheritDoc}
*/
@Override
public List<T> loadObjects()
{
try (Connection connection = this.dataSource.getConnection();
Statement preparedStatement = connection.createStatement())
{
return this.loadIt(preparedStatement);
}
catch (SQLException e)
{
this.plugin.logError(COULD_NOT_LOAD_OBJECTS + e.getMessage());
}
return Collections.emptyList();
}
private List<T> loadIt(Statement preparedStatement) {
/**
* This method loads objects based on results provided by prepared statement.
* @param preparedStatement Statement from database.
* @return List of object <T> from database.
*/
private List<T> loadIt(Statement preparedStatement)
{
List<T> list = new ArrayList<>();
try (ResultSet resultSet = preparedStatement.executeQuery(sqlConfig.getLoadObjectsSQL())) {
try (ResultSet resultSet = preparedStatement.executeQuery(this.sqlConfig.getLoadObjectsSQL()))
{
// Load all the results
Gson gson = getGson();
while (resultSet.next()) {
Gson gson = this.getGson();
while (resultSet.next())
{
String json = resultSet.getString("json");
if (json != null) {
try {
T gsonResult = gson.fromJson(json, dataObject);
if (gsonResult != null) {
list.add(gsonResult);
}
} catch (JsonSyntaxException ex) {
plugin.logError(COULD_NOT_LOAD_OBJECT + ex.getMessage());
plugin.logError(json);
if (json != null)
{
getGsonResultSet(gson, json, list);
}
}
}
} catch (Exception e) {
plugin.logError(COULD_NOT_LOAD_OBJECTS + e.getMessage());
catch (Exception e)
{
this.plugin.logError(COULD_NOT_LOAD_OBJECTS + e.getMessage());
}
return list;
}
private void getGsonResultSet(Gson gson, String json, List<T> list) {
try
{
T gsonResult = gson.fromJson(json, this.dataObject);
if (gsonResult != null)
{
list.add(gsonResult);
}
}
catch (JsonSyntaxException ex)
{
this.plugin.logError(COULD_NOT_LOAD_OBJECT + ex.getMessage());
this.plugin.logError(json);
}
}
/**
* {@inheritDoc}
*/
@Override
public T loadObject(@NonNull String uniqueId) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sqlConfig.getLoadObjectSQL())) {
public T loadObject(@NonNull String uniqueId)
{
T result = null;
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(this.sqlConfig.getLoadObjectSQL()))
{
// UniqueId needs to be placed in quotes?
preparedStatement.setString(1, this.sqlConfig.isUseQuotes() ? "\"" + uniqueId + "\"" : uniqueId);
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
result = getObject(uniqueId, preparedStatement);
}
catch (SQLException e)
{
this.plugin.logError(COULD_NOT_LOAD_OBJECT + uniqueId + " " + e.getMessage());
}
return result;
}
/**
* Return the object decoded from JSON or null if there is an error
* @param uniqueId - unique Id of object used in error reporting
* @param preparedStatement - database statement to execute
* @return
*/
private T getObject(@NonNull String uniqueId, PreparedStatement preparedStatement) {
try (ResultSet resultSet = preparedStatement.executeQuery())
{
if (resultSet.next())
{
// If there is a result, we only want/need the first one
Gson gson = getGson();
return gson.fromJson(resultSet.getString("json"), dataObject);
Gson gson = this.getGson();
return gson.fromJson(resultSet.getString("json"), this.dataObject);
}
} catch (Exception e) {
plugin.logError(COULD_NOT_LOAD_OBJECT + uniqueId + " " + e.getMessage());
}
} catch (SQLException e) {
plugin.logError(COULD_NOT_LOAD_OBJECT + uniqueId + " " + e.getMessage());
catch (Exception e)
{
this.plugin.logError(COULD_NOT_LOAD_OBJECT + uniqueId + " " + e.getMessage());
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public CompletableFuture<Boolean> saveObject(T instance) {
public CompletableFuture<Boolean> saveObject(T instance)
{
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
// Null check
if (instance == null) {
plugin.logError("SQL database request to store a null. ");
if (instance == null)
{
this.plugin.logError("SQL database request to store a null. ");
completableFuture.complete(false);
return completableFuture;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
completableFuture.complete(false);
return completableFuture;
}
// This has to be on the main thread to avoid concurrent modification errors
String toStore = getGson().toJson(instance);
if (plugin.isEnabled()) {
// Async
processQueue.add(() -> store(completableFuture, instance.getClass().getName(), toStore, sqlConfig.getSaveObjectSQL(), true));
} else {
// Sync
store(completableFuture, instance.getClass().getName(), toStore, sqlConfig.getSaveObjectSQL(), false);
}
return completableFuture;
}
private void store(CompletableFuture<Boolean> completableFuture, String name, String toStore, String sb, boolean async) {
if (!(instance instanceof DataObject))
{
this.plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
completableFuture.complete(false);
return completableFuture;
}
// This has to be on the main thread to avoid concurrent modification errors
String toStore = this.getGson().toJson(instance);
if (this.plugin.isEnabled())
{
// Async
this.processQueue.add(() -> store(completableFuture,
instance.getClass().getName(),
toStore,
this.sqlConfig.getSaveObjectSQL(),
true));
}
else
{
// Sync
this.store(completableFuture, instance.getClass().getName(), toStore, this.sqlConfig.getSaveObjectSQL(), false);
}
return completableFuture;
}
/**
* This method is called to save data into database based on given parameters.
* @param completableFuture Failsafe on saving data.
* @param name Name of the class that is saved.
* @param toStore data that is stored.
* @param storeSQL SQL command for saving.
* @param async boolean that indicates if saving is async or not.
*/
private void store(CompletableFuture<Boolean> completableFuture, String name, String toStore, String storeSQL, boolean async)
{
// Do not save anything if plug is disabled and this was an async request
if (async && !plugin.isEnabled()) return;
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
if (async && !this.plugin.isEnabled())
{
return;
}
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(storeSQL))
{
preparedStatement.setString(1, toStore);
preparedStatement.setString(2, toStore);
preparedStatement.execute();
completableFuture.complete(true);
} catch (SQLException e) {
plugin.logError("Could not save object " + name + " " + e.getMessage());
}
catch (SQLException e)
{
this.plugin.logError("Could not save object " + name + " " + e.getMessage());
completableFuture.complete(false);
}
}
/* (non-Javadoc)
* @see world.bentobox.bentobox.database.AbstractDatabaseHandler#deleteID(java.lang.String)
/**
* {@inheritDoc}
*/
@Override
public void deleteID(String uniqueId) {
processQueue.add(() -> delete(uniqueId));
public void deleteID(String uniqueId)
{
this.processQueue.add(() -> this.delete(uniqueId));
}
private void delete(String uniqueId) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sqlConfig.getDeleteObjectSQL())) {
/**
* This method triggers object deletion from the database.
* @param uniqueId Object unique id.
*/
private void delete(String uniqueId)
{
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(this.sqlConfig.getDeleteObjectSQL()))
{
// UniqueId needs to be placed in quotes?
preparedStatement.setString(1, this.sqlConfig.isUseQuotes() ? "\"" + uniqueId + "\"" : uniqueId);
preparedStatement.execute();
} catch (Exception e) {
plugin.logError("Could not delete object " + plugin.getSettings().getDatabasePrefix() + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
}
catch (Exception e)
{
this.plugin.logError("Could not delete object " + this.plugin.getSettings().getDatabasePrefix() +
this.dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
}
}
/**
* {@inheritDoc}
*/
@Override
public void deleteObject(T instance) {
public void deleteObject(T instance)
{
// Null check
if (instance == null) {
plugin.logError("SQL database request to delete a null.");
if (instance == null)
{
this.plugin.logError("SQL database request to delete a null.");
return;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
if (!(instance instanceof DataObject))
{
this.plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
try {
Method getUniqueId = dataObject.getMethod("getUniqueId");
deleteID((String) getUniqueId.invoke(instance));
} catch (Exception e) {
plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
try
{
Method getUniqueId = this.dataObject.getMethod("getUniqueId");
this.deleteID((String) getUniqueId.invoke(instance));
}
catch (Exception e)
{
this.plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean objectExists(String uniqueId) {
public boolean objectExists(String uniqueId)
{
// Query to see if this key exists
try (PreparedStatement preparedStatement = connection.prepareStatement(sqlConfig.getObjectExistsSQL())) {
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(this.sqlConfig.getObjectExistsSQL()))
{
// UniqueId needs to be placed in quotes?
preparedStatement.setString(1, this.sqlConfig.isUseQuotes() ? "\"" + uniqueId + "\"" : uniqueId);
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
try (ResultSet resultSet = preparedStatement.executeQuery())
{
if (resultSet.next())
{
return resultSet.getBoolean(1);
}
}
} catch (SQLException e) {
plugin.logError("Could not check if key exists in database! " + uniqueId + " " + e.getMessage());
}
catch (SQLException e)
{
this.plugin.logError("Could not check if key exists in database! " + uniqueId + " " + e.getMessage());
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void close() {
shutdown = true;
public void close()
{
this.shutdown = true;
}
/**
* @return the connection
*/
public Connection getConnection() {
return connection;
}
/**
* @param connection the connection to set
* @return true if connection is not null
* Sets data source of database.
*
* @param dataSource the data source
* @return {@code true} if data source is set, {@code false} otherwise.
*/
public boolean setConnection(Connection connection) {
if (connection == null) {
plugin.logError("Could not connect to the database. Are the credentials in the config.yml file correct?");
plugin.logWarning("Disabling the plugin...");
Bukkit.getPluginManager().disablePlugin(plugin);
public boolean setDataSource(DataSource dataSource)
{
if (dataSource == null)
{
this.plugin.logError("Could not connect to the database. Are the credentials in the config.yml file correct?");
this.plugin.logWarning("Disabling the plugin...");
Bukkit.getPluginManager().disablePlugin(this.plugin);
return false;
}
this.connection = connection;
this.dataSource = dataSource;
return true;
}
}

View File

@ -9,26 +9,34 @@ import world.bentobox.bentobox.database.DatabaseSetup;
* @author barpec12
* @since 1.1
*/
public class MariaDBDatabase implements DatabaseSetup {
public class MariaDBDatabase implements DatabaseSetup
{
/**
* MariaDB Database Connector.
*/
private MariaDBDatabaseConnector connector;
/* (non-Javadoc)
* @see world.bentobox.bentobox.database.DatabaseSetup#getHandler(java.lang.Class)
/**
* {@inheritDoc}
*/
@Override
public <T> AbstractDatabaseHandler<T> getHandler(Class<T> type) {
public <T> AbstractDatabaseHandler<T> getHandler(Class<T> type)
{
BentoBox plugin = BentoBox.getInstance();
if (connector == null) {
connector = new MariaDBDatabaseConnector(new DatabaseConnectionSettingsImpl(
if (this.connector == null)
{
this.connector = new MariaDBDatabaseConnector(new DatabaseConnectionSettingsImpl(
plugin.getSettings().getDatabaseHost(),
plugin.getSettings().getDatabasePort(),
plugin.getSettings().getDatabaseName(),
plugin.getSettings().getDatabaseUsername(),
plugin.getSettings().getDatabasePassword(),
plugin.getSettings().isUseSSL()
));
}
return new MariaDBDatabaseHandler<>(plugin, type, connector);
plugin.getSettings().isUseSSL(),
plugin.getSettings().getMaximumPoolSize()));
}
return new MariaDBDatabaseHandler<>(plugin, type, this.connector);
}
}

View File

@ -1,5 +1,9 @@
package world.bentobox.bentobox.database.sql.mariadb;
import org.eclipse.jdt.annotation.NonNull;
import com.zaxxer.hikari.HikariConfig;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.sql.SQLDatabaseConnector;
@ -7,15 +11,45 @@ import world.bentobox.bentobox.database.sql.SQLDatabaseConnector;
* @author barpec12
* @since 1.1
*/
public class MariaDBDatabaseConnector extends SQLDatabaseConnector {
public class MariaDBDatabaseConnector extends SQLDatabaseConnector
{
/**
* Class for MariaDB database connections using the settings provided
* @param dbSettings - database settings
*/
MariaDBDatabaseConnector(DatabaseConnectionSettingsImpl dbSettings) {
super(dbSettings, "jdbc:mysql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName()
+ "?autoReconnect=true&useSSL=" + dbSettings.isUseSSL() + "&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8");
MariaDBDatabaseConnector(@NonNull DatabaseConnectionSettingsImpl dbSettings)
{
// MariaDB does not use connectionUrl.
super(dbSettings, String.format("jdbc:mariadb://%s:%s/%s",
dbSettings.getHost(),
dbSettings.getPort(),
dbSettings.getDatabaseName()));
}
/**
* {@inheritDoc}
*/
@Override
public HikariConfig createConfig()
{
HikariConfig config = new HikariConfig();
config.setPoolName("BentoBox MariaDB Pool");
config.setDriverClassName("org.mariadb.jdbc.Driver");
config.setJdbcUrl(this.connectionUrl);
config.addDataSourceProperty("user", this.dbSettings.getUsername());
config.addDataSourceProperty("password", this.dbSettings.getPassword());
config.addDataSourceProperty("useSsl", this.dbSettings.isUseSSL());
config.addDataSourceProperty("allowMultiQueries", "true");
// Add extra properties.
this.dbSettings.getExtraProperties().forEach(config::addDataSourceProperty);
config.setMaximumPoolSize(this.dbSettings.getMaxConnections());
return config;
}
}

View File

@ -13,8 +13,8 @@ import world.bentobox.bentobox.database.sql.SQLDatabaseHandler;
*
* @param <T>
*/
public class MariaDBDatabaseHandler<T> extends SQLDatabaseHandler<T> {
public class MariaDBDatabaseHandler<T> extends SQLDatabaseHandler<T>
{
/**
* Handles the connection to the database and creation of the initial database schema (tables) for
* the class that will be stored.
@ -22,9 +22,11 @@ public class MariaDBDatabaseHandler<T> extends SQLDatabaseHandler<T> {
* @param type - the type of class to be stored in the database. Must inherit DataObject
* @param databaseConnector - authentication details for the database
*/
MariaDBDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) {
super(plugin, type, databaseConnector,
new SQLConfiguration(plugin, type)
.schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))"));
MariaDBDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector)
{
super(plugin,
type,
databaseConnector,
new SQLConfiguration(plugin, type).schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(json, \"$.uniqueId\")), UNIQUE INDEX i (uniqueId))"));
}
}

View File

@ -5,27 +5,34 @@ import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.DatabaseSetup;
public class MySQLDatabase implements DatabaseSetup {
public class MySQLDatabase implements DatabaseSetup
{
/**
* MySQL Database Connector
*/
private MySQLDatabaseConnector connector;
/* (non-Javadoc)
* @see world.bentobox.bentobox.database.DatabaseSetup#getHandler(java.lang.Class)
/**
* {@inheritDoc}
*/
@Override
public <T> AbstractDatabaseHandler<T> getHandler(Class<T> type) {
public <T> AbstractDatabaseHandler<T> getHandler(Class<T> type)
{
BentoBox plugin = BentoBox.getInstance();
if (connector == null) {
connector = new MySQLDatabaseConnector(new DatabaseConnectionSettingsImpl(
if (this.connector == null)
{
this.connector = new MySQLDatabaseConnector(new DatabaseConnectionSettingsImpl(
plugin.getSettings().getDatabaseHost(),
plugin.getSettings().getDatabasePort(),
plugin.getSettings().getDatabaseName(),
plugin.getSettings().getDatabaseUsername(),
plugin.getSettings().getDatabasePassword(),
plugin.getSettings().isUseSSL()
));
}
return new MySQLDatabaseHandler<>(plugin, type, connector);
plugin.getSettings().isUseSSL(),
plugin.getSettings().getMaximumPoolSize()));
}
return new MySQLDatabaseHandler<>(plugin, type, this.connector);
}
}

View File

@ -1,16 +1,56 @@
package world.bentobox.bentobox.database.sql.mysql;
import org.eclipse.jdt.annotation.NonNull;
import com.zaxxer.hikari.HikariConfig;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.sql.SQLDatabaseConnector;
public class MySQLDatabaseConnector extends SQLDatabaseConnector {
public class MySQLDatabaseConnector extends SQLDatabaseConnector
{
/**
* Class for MySQL database connections using the settings provided
*
* @param dbSettings - database settings
*/
MySQLDatabaseConnector(DatabaseConnectionSettingsImpl dbSettings) {
super(dbSettings, "jdbc:mysql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName()
+ "?autoReconnect=true&useSSL=" + dbSettings.isUseSSL() + "&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8");
MySQLDatabaseConnector(@NonNull DatabaseConnectionSettingsImpl dbSettings)
{
super(dbSettings, String.format("jdbc:mysql://%s:%s/%s",
dbSettings.getHost(),
dbSettings.getPort(),
dbSettings.getDatabaseName()));
}
/**
* {@inheritDoc}
*/
@Override
public HikariConfig createConfig()
{
HikariConfig config = new HikariConfig();
config.setPoolName("BentoBox MySQL Pool");
config.setDriverClassName("com.mysql.jdbc.Driver");
config.setJdbcUrl(this.connectionUrl);
config.setUsername(this.dbSettings.getUsername());
config.setPassword(this.dbSettings.getPassword());
config.addDataSourceProperty("useSSL", this.dbSettings.isUseSSL());
config.addDataSourceProperty("characterEncoding", "utf8");
config.addDataSourceProperty("encoding", "UTF-8");
config.addDataSourceProperty("useUnicode", "true");
config.addDataSourceProperty("allowMultiQueries", "true");
config.addDataSourceProperty("allowPublicKeyRetrieval", "true");
// Add extra properties.
this.dbSettings.getExtraProperties().forEach(config::addDataSourceProperty);
config.setMaximumPoolSize(this.dbSettings.getMaxConnections());
return config;
}
}

View File

@ -13,17 +13,21 @@ import world.bentobox.bentobox.database.sql.SQLDatabaseHandler;
*
* @param <T>
*/
public class MySQLDatabaseHandler<T> extends SQLDatabaseHandler<T> {
public class MySQLDatabaseHandler<T> extends SQLDatabaseHandler<T>
{
/**
* Handles the connection to the database and creation of the initial database schema (tables) for
* the class that will be stored.
* Handles the connection to the database and creation of the initial database schema (tables) for the class that
* will be stored.
*
* @param plugin - plugin object
* @param type - the type of class to be stored in the database. Must inherit DataObject
* @param dbConnecter - authentication details for the database
*/
MySQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector dbConnecter) {
super(plugin, type, dbConnecter, new SQLConfiguration(plugin, type)
.schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB"));
MySQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector dbConnecter)
{
super(plugin,
type,
dbConnecter,
new SQLConfiguration(plugin, type).schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) GENERATED ALWAYS AS (json->\"$.uniqueId\"), UNIQUE INDEX i (uniqueId) ) ENGINE = INNODB"));
}
}

View File

@ -9,23 +9,34 @@ import world.bentobox.bentobox.database.DatabaseSetup;
* @since 1.6.0
* @author Poslovitch
*/
public class PostgreSQLDatabase implements DatabaseSetup {
public class PostgreSQLDatabase implements DatabaseSetup
{
/**
* PostgreSQL Database Connector.
*/
PostgreSQLDatabaseConnector connector;
/**
* {@inheritDoc}
*/
@Override
public <T> AbstractDatabaseHandler<T> getHandler(Class<T> dataObjectClass) {
public <T> AbstractDatabaseHandler<T> getHandler(Class<T> dataObjectClass)
{
BentoBox plugin = BentoBox.getInstance();
if (connector == null) {
connector = new PostgreSQLDatabaseConnector(new DatabaseConnectionSettingsImpl(
if (this.connector == null)
{
this.connector = new PostgreSQLDatabaseConnector(new DatabaseConnectionSettingsImpl(
plugin.getSettings().getDatabaseHost(),
plugin.getSettings().getDatabasePort(),
plugin.getSettings().getDatabaseName(),
plugin.getSettings().getDatabaseUsername(),
plugin.getSettings().getDatabasePassword(),
plugin.getSettings().isUseSSL()
));
plugin.getSettings().isUseSSL(),
plugin.getSettings().getMaximumPoolSize()));
}
return new PostgreSQLDatabaseHandler<>(plugin, dataObjectClass, connector);
return new PostgreSQLDatabaseHandler<>(plugin, dataObjectClass, this.connector);
}
}

View File

@ -1,34 +1,54 @@
package world.bentobox.bentobox.database.sql.postgresql;
import org.eclipse.jdt.annotation.NonNull;
import org.postgresql.Driver;
import com.zaxxer.hikari.HikariConfig;
import world.bentobox.bentobox.database.DatabaseConnectionSettingsImpl;
import world.bentobox.bentobox.database.sql.SQLDatabaseConnector;
/**
* @since 1.6.0
* @author Poslovitch
*/
public class PostgreSQLDatabaseConnector extends SQLDatabaseConnector {
/*
* Ensure the driver is loaded as JDBC Driver might be invisible to Java's ServiceLoader.
* Usually, this is not required as {@link DriverManager} detects JDBC drivers
* via {@code META-INF/services/java.sql.Driver} entries. However there might be cases when the driver
* is located at the application level classloader, thus it might be required to perform manual
* registration of the driver.
*/
static {
new Driver();
}
public class PostgreSQLDatabaseConnector extends SQLDatabaseConnector
{
/**
* Class for PostgreSQL database connections using the settings provided
*
* @param dbSettings - database settings
*/
PostgreSQLDatabaseConnector(@NonNull DatabaseConnectionSettingsImpl dbSettings) {
super(dbSettings, "jdbc:postgresql://" + dbSettings.getHost() + ":" + dbSettings.getPort() + "/" + dbSettings.getDatabaseName()
+ "?autoReconnect=true&useSSL=" + dbSettings.isUseSSL() + "&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8");
PostgreSQLDatabaseConnector(@NonNull DatabaseConnectionSettingsImpl dbSettings)
{
// connectionUrl is not used in PostgreSQL connection.
super(dbSettings, "");
}
/**
* {@inheritDoc}
*/
@Override
public HikariConfig createConfig()
{
HikariConfig config = new HikariConfig();
config.setPoolName("BentoBox PostgreSQL Pool");
config.setDataSourceClassName("org.postgresql.ds.PGSimpleDataSource");
config.addDataSourceProperty("user", this.dbSettings.getUsername());
config.addDataSourceProperty("password", this.dbSettings.getPassword());
config.addDataSourceProperty("databaseName", this.dbSettings.getDatabaseName());
config.addDataSourceProperty("serverName", this.dbSettings.getHost());
config.addDataSourceProperty("portNumber", this.dbSettings.getPort());
config.addDataSourceProperty("ssl", this.dbSettings.isUseSSL());
// Add extra properties.
this.dbSettings.getExtraProperties().forEach(config::addDataSourceProperty);
config.setMaximumPoolSize(this.dbSettings.getMaxConnections());
return config;
}
}

View File

@ -1,5 +1,6 @@
package world.bentobox.bentobox.database.sql.postgresql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.concurrent.CompletableFuture;
@ -19,70 +20,86 @@ import world.bentobox.bentobox.database.sql.SQLDatabaseHandler;
* @since 1.11.0
* @author tastybento
*/
public class PostgreSQLDatabaseHandler<T> extends SQLDatabaseHandler<T> {
public class PostgreSQLDatabaseHandler<T> extends SQLDatabaseHandler<T>
{
/**
* Constructor
*
* @param plugin BentoBox plugin
* @param type The type of the objects that should be created and filled with
* values from the database or inserted into the database
* @param type The type of the objects that should be created and filled with values from the database or inserted
* into the database
* @param databaseConnector Contains the settings to create a connection to the database
*/
PostgreSQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) {
super(plugin, type, databaseConnector, new SQLConfiguration(plugin, type)
PostgreSQLDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector)
{
super(plugin,
type,
databaseConnector,
new SQLConfiguration(plugin, type).
// Set uniqueid as the primary key (index). Postgresql convention is to use lower case field names
// Postgresql also uses double quotes (") instead of (`) around tables names with dots.
.schema("CREATE TABLE IF NOT EXISTS \"[tableName]\" (uniqueid VARCHAR PRIMARY KEY, json jsonb NOT NULL)")
.loadObject("SELECT * FROM \"[tableName]\" WHERE uniqueid = ? LIMIT 1")
.deleteObject("DELETE FROM \"[tableName]\" WHERE uniqueid = ?")
schema("CREATE TABLE IF NOT EXISTS \"[tableName]\" (uniqueid VARCHAR PRIMARY KEY, json jsonb NOT NULL)").
loadObject("SELECT * FROM \"[tableName]\" WHERE uniqueid = ? LIMIT 1").
deleteObject("DELETE FROM \"[tableName]\" WHERE uniqueid = ?").
// uniqueId has to be added into the row explicitly so we need to override the saveObject method
// The json value is a string but has to be cast to json when done in Java
.saveObject("INSERT INTO \"[tableName]\" (uniqueid, json) VALUES (?, cast(? as json)) "
saveObject("INSERT INTO \"[tableName]\" (uniqueid, json) VALUES (?, cast(? as json)) "
// This is the Postgresql version of UPSERT.
+ "ON CONFLICT (uniqueid) "
+ "DO UPDATE SET json = cast(? as json)")
.loadObjects("SELECT json FROM \"[tableName]\"")
+ "ON CONFLICT (uniqueid) DO UPDATE SET json = cast(? as json)").
loadObjects("SELECT json FROM \"[tableName]\"").
// Postgres exists function returns true or false natively
.objectExists("SELECT EXISTS(SELECT * FROM \"[tableName]\" WHERE uniqueid = ?)")
.renameTable("ALTER TABLE IF EXISTS \"[oldTableName]\" RENAME TO \"[tableName]\"")
.setUseQuotes(false)
objectExists("SELECT EXISTS(SELECT * FROM \"[tableName]\" WHERE uniqueid = ?)").
renameTable("ALTER TABLE IF EXISTS \"[oldTableName]\" RENAME TO \"[tableName]\"").
setUseQuotes(false)
);
}
/* (non-Javadoc)
* @see world.bentobox.bentobox.database.sql.SQLDatabaseHandler#saveObject(java.lang.Object)
/**
* {@inheritDoc}
*/
@Override
public CompletableFuture<Boolean> saveObject(T instance) {
public CompletableFuture<Boolean> saveObject(T instance)
{
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
// Null check
if (instance == null) {
plugin.logError("PostgreSQL database request to store a null. ");
if (instance == null)
{
this.plugin.logError("PostgreSQL database request to store a null. ");
completableFuture.complete(false);
return completableFuture;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
if (!(instance instanceof DataObject))
{
this.plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
completableFuture.complete(false);
return completableFuture;
}
Gson gson = getGson();
Gson gson = this.getGson();
String toStore = gson.toJson(instance);
String uniqueId = ((DataObject)instance).getUniqueId();
processQueue.add(() -> {
try (PreparedStatement preparedStatement = getConnection().prepareStatement(getSqlConfig().getSaveObjectSQL())) {
String uniqueId = ((DataObject) instance).getUniqueId();
this.processQueue.add(() ->
{
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(this.getSqlConfig().getSaveObjectSQL()))
{
preparedStatement.setString(1, uniqueId); // INSERT
preparedStatement.setString(2, toStore); // INSERT
preparedStatement.setString(3, toStore); // ON CONFLICT
preparedStatement.execute();
completableFuture.complete(true);
} catch (SQLException e) {
plugin.logError("Could not save object " + instance.getClass().getName() + " " + e.getMessage());
}
catch (SQLException e)
{
this.plugin.logError("Could not save object " + instance.getClass().getName() + " " + e.getMessage());
completableFuture.complete(false);
}
});
return completableFuture;
}
}

View File

@ -1,19 +1,51 @@
package world.bentobox.bentobox.database.sql.sqlite;
import java.io.File;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup;
/**
* @since 1.6.0
* @author Poslovitch
*/
public class SQLiteDatabase implements DatabaseSetup {
public class SQLiteDatabase implements DatabaseSetup
{
/**
* Database file name.
*/
private static final String DATABASE_FOLDER_NAME = "database";
private final SQLiteDatabaseConnector connector = new SQLiteDatabaseConnector(BentoBox.getInstance());
/**
* SQLite Database Connector.
*/
private SQLiteDatabaseConnector connector;
/**
* {@inheritDoc}
*/
@Override
public <T> AbstractDatabaseHandler<T> getHandler(Class<T> dataObjectClass) {
return new SQLiteDatabaseHandler<>(BentoBox.getInstance(), dataObjectClass, connector);
public <T> AbstractDatabaseHandler<T> getHandler(Class<T> dataObjectClass)
{
if (this.connector == null)
{
BentoBox plugin = BentoBox.getInstance();
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
if (!dataFolder.exists() && !dataFolder.mkdirs())
{
plugin.logError("Could not create database folder!");
// Trigger plugin shutdown.
plugin.onDisable();
return null;
}
this.connector = new SQLiteDatabaseConnector("jdbc:sqlite:" + dataFolder.getAbsolutePath() + File.separator + "database.db");
}
return new SQLiteDatabaseHandler<>(BentoBox.getInstance(), dataObjectClass, this.connector);
}
}

View File

@ -1,48 +1,37 @@
package world.bentobox.bentobox.database.sql.sqlite;
import java.io.File;
import java.sql.DriverManager;
import java.sql.SQLException;
import com.zaxxer.hikari.HikariConfig;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.sql.SQLDatabaseConnector;
/**
* @since 1.6.0
* @author Poslovitch
*/
public class SQLiteDatabaseConnector extends SQLDatabaseConnector {
private static final String DATABASE_FOLDER_NAME = "database";
SQLiteDatabaseConnector(@NonNull BentoBox plugin) {
super(null, ""); // Not used by SQLite
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
if (!dataFolder.exists() && !dataFolder.mkdirs()) {
BentoBox.getInstance().logError("Could not create database folder!");
return;
}
connectionUrl = "jdbc:sqlite:" + dataFolder.getAbsolutePath() + File.separator + "database.db";
public class SQLiteDatabaseConnector extends SQLDatabaseConnector
{
/**
* Default constructor.
*/
SQLiteDatabaseConnector(String connectionUrl)
{
super(null, connectionUrl);
}
/* (non-Javadoc)
* @see world.bentobox.bentobox.database.sql.SQLDatabaseConnector#createConnection(java.lang.Class)
/**
* {@inheritDoc}
*/
@Override
public Object createConnection(Class<?> type) {
types.add(type);
// Only make one connection at a time
if (connection == null) {
try {
connection = DriverManager.getConnection(connectionUrl);
} catch (SQLException e) {
Bukkit.getLogger().severe("Could not connect to the database! " + e.getMessage());
}
}
return connection;
public HikariConfig createConfig()
{
HikariConfig config = new HikariConfig();
config.setDataSourceClassName("org.sqlite.SQLiteDataSource");
config.setPoolName("BentoBox SQLite Pool");
config.addDataSourceProperty("encoding", "UTF-8");
config.addDataSourceProperty("url", this.connectionUrl);
config.setMaximumPoolSize(100);
return config;
}
}

View File

@ -1,5 +1,6 @@
package world.bentobox.bentobox.database.sql.sqlite;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -17,23 +18,24 @@ import world.bentobox.bentobox.database.sql.SQLDatabaseHandler;
* @since 1.6.0
* @author Poslovitch, tastybento
*/
public class SQLiteDatabaseHandler<T> extends SQLDatabaseHandler<T> {
public class SQLiteDatabaseHandler<T> extends SQLDatabaseHandler<T>
{
/**
* Constructor
*
* @param plugin BentoBox plugin
* @param type The type of the objects that should be created and filled with
* values from the database or inserted into the database
* @param type The type of the objects that should be created and filled with values from the database or inserted
* into the database
* @param databaseConnector Contains the settings to create a connection to the database
*/
protected SQLiteDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) {
super(plugin, type, databaseConnector, new SQLConfiguration(plugin, type)
.schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) NOT NULL PRIMARY KEY)")
.saveObject("INSERT INTO `[tableName]` (json, uniqueId) VALUES (?, ?) ON CONFLICT(uniqueId) DO UPDATE SET json = ?")
.objectExists("SELECT EXISTS (SELECT 1 FROM `[tableName]` WHERE `uniqueId` = ?)")
.renameTable("ALTER TABLE `[oldTableName]` RENAME TO `[tableName]`")
.setUseQuotes(false)
protected SQLiteDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector)
{
super(plugin, type, databaseConnector, new SQLConfiguration(plugin, type).
schema("CREATE TABLE IF NOT EXISTS `[tableName]` (json JSON, uniqueId VARCHAR(255) NOT NULL PRIMARY KEY)").
saveObject("INSERT INTO `[tableName]` (json, uniqueId) VALUES (?, ?) ON CONFLICT(uniqueId) DO UPDATE SET json = ?").
objectExists("SELECT EXISTS (SELECT 1 FROM `[tableName]` WHERE `uniqueId` = ?)").
renameTable("ALTER TABLE `[oldTableName]` RENAME TO `[tableName]`").
setUseQuotes(false)
);
}
@ -42,70 +44,115 @@ public class SQLiteDatabaseHandler<T> extends SQLDatabaseHandler<T> {
* Creates the table in the database if it doesn't exist already
*/
@Override
protected void createSchema() {
if (getSqlConfig().renameRequired()) {
protected void createSchema()
{
if (this.getSqlConfig().renameRequired())
{
// SQLite does not have a rename if exists command so we have to manually check if the old table exists
String sql = "SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type='table' AND name='" + getSqlConfig().getOldTableName() + "' COLLATE NOCASE)";
try (PreparedStatement pstmt = getConnection().prepareStatement(sql)) {
rename(pstmt);
} catch (SQLException e) {
plugin.logError("Could not check if " + getSqlConfig().getOldTableName() + " exists for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
String sql = "SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type='table' AND name='" +
this.getSqlConfig().getOldTableName() + "' COLLATE NOCASE)";
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql))
{
this.rename(preparedStatement);
}
catch (SQLException e)
{
this.plugin.logError("Could not check if " + this.getSqlConfig().getOldTableName() + " exists for data object " +
this.dataObject.getCanonicalName() + " " + e.getMessage());
}
}
// Prepare and execute the database statements
try (PreparedStatement pstmt = getConnection().prepareStatement(getSqlConfig().getSchemaSQL())) {
pstmt.execute();
} catch (SQLException e) {
plugin.logError("Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(this.getSqlConfig().getSchemaSQL()))
{
preparedStatement.execute();
}
catch (SQLException e)
{
this.plugin.logError("Problem trying to create schema for data object " + dataObject.getCanonicalName() + " " +
e.getMessage());
}
}
private void rename(PreparedStatement pstmt) {
try (ResultSet resultSet = pstmt.executeQuery()) {
if (resultSet.next() && resultSet.getBoolean(1)) {
private void rename(PreparedStatement pstmt)
{
try (ResultSet resultSet = pstmt.executeQuery())
{
if (resultSet.next() && resultSet.getBoolean(1))
{
// Transition from the old table name
String sql = getSqlConfig().getRenameTableSQL().replace("[oldTableName]", getSqlConfig().getOldTableName().replace("[tableName]", getSqlConfig().getTableName()));
try (PreparedStatement pstmt2 = getConnection().prepareStatement(sql)) {
pstmt2.execute();
} catch (SQLException e) {
plugin.logError("Could not rename " + getSqlConfig().getOldTableName() + " for data object " + dataObject.getCanonicalName() + " " + e.getMessage());
String sql = this.getSqlConfig().getRenameTableSQL().replace("[oldTableName]",
this.getSqlConfig().getOldTableName().replace("[tableName]", this.getSqlConfig().getTableName()));
executeStatement(sql);
}
}
} catch (Exception ex) {
plugin.logError("Could not check if " + getSqlConfig().getOldTableName() + " exists for data object " + dataObject.getCanonicalName() + " " + ex.getMessage());
catch (Exception ex)
{
this.plugin.logError("Could not check if " + getSqlConfig().getOldTableName() + " exists for data object " +
this.dataObject.getCanonicalName() + " " + ex.getMessage());
}
}
private void executeStatement(String sql) {
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql))
{
preparedStatement.execute();
}
catch (SQLException e)
{
this.plugin.logError("Could not rename " + getSqlConfig().getOldTableName() + " for data object " +
this.dataObject.getCanonicalName() + " " + e.getMessage());
}
}
@Override
public CompletableFuture<Boolean> saveObject(T instance) {
public CompletableFuture<Boolean> saveObject(T instance)
{
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
// Null check
if (instance == null) {
plugin.logError("SQLite database request to store a null. ");
if (instance == null)
{
this.plugin.logError("SQLite database request to store a null. ");
completableFuture.complete(false);
return completableFuture;
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
if (!(instance instanceof DataObject))
{
this.plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
completableFuture.complete(false);
return completableFuture;
}
Gson gson = getGson();
Gson gson = this.getGson();
String toStore = gson.toJson(instance);
processQueue.add(() -> {
try (PreparedStatement preparedStatement = getConnection().prepareStatement(getSqlConfig().getSaveObjectSQL())) {
this.processQueue.add(() ->
{
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(this.getSqlConfig().getSaveObjectSQL()))
{
preparedStatement.setString(1, toStore);
preparedStatement.setString(2, ((DataObject)instance).getUniqueId());
preparedStatement.setString(2, ((DataObject) instance).getUniqueId());
preparedStatement.setString(3, toStore);
preparedStatement.execute();
completableFuture.complete(true);
} catch (SQLException e) {
plugin.logError("Could not save object " + instance.getClass().getName() + " " + e.getMessage());
}
catch (SQLException e)
{
this.plugin.logError("Could not save object " + instance.getClass().getName() + " " + ((DataObject) instance).getUniqueId() + " " + e.getMessage());
completableFuture.complete(false);
}
});
return completableFuture;
}
}

View File

@ -25,8 +25,6 @@ import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.eclipse.jdt.annotation.NonNull;
import com.google.common.base.Charsets;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.DatabaseConnector;
@ -100,11 +98,14 @@ public class YamlDatabaseConnector implements DatabaseConnector {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(yamlFile), StandardCharsets.UTF_8))){
File temp = File.createTempFile("file", ".tmp", yamlFile.getParentFile());
writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(temp), StandardCharsets.UTF_8));
for (String line; (line = reader.readLine()) != null;) {
String line = reader.readLine();
while (line != null) {
line = line.replace("!!java.util.UUID", "");
writer.println(line);
line = reader.readLine();
}
if (yamlFile.delete() && !temp.renameTo(yamlFile)) {
Files.delete(yamlFile.toPath());
if (!temp.renameTo(yamlFile)) {
plugin.logError("Could not rename fixed Island object. Are the writing permissions correctly setup?");
}
} catch (Exception e) {
@ -123,7 +124,7 @@ public class YamlDatabaseConnector implements DatabaseConnector {
if (!tableFolder.exists()) {
tableFolder.mkdirs();
}
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)) {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
writer.write(data);
} catch (IOException e) {
plugin.logError("Could not save yml file: " + tableName + " " + fileName + " " + e.getMessage());
@ -151,9 +152,8 @@ public class YamlDatabaseConnector implements DatabaseConnector {
for (Entry<String, String> e : commentMap.entrySet()) {
if (nextLine.contains(e.getKey())) {
// We want the comment to start at the same level as the entry
String commentLine = " ".repeat(Math.max(0, nextLine.indexOf(e.getKey()))) +
nextLine = " ".repeat(Math.max(0, nextLine.indexOf(e.getKey()))) +
e.getValue();
nextLine = commentLine;
break;
}
}

View File

@ -316,9 +316,9 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
// There could be more than one argument, so step through them
for (Type genericParameterType : genericParameterTypes) {
// If the argument is a parameter, then do something - this should always be true if the parameter is a collection
if(genericParameterType instanceof ParameterizedType ) {
if(genericParameterType instanceof ParameterizedType pt) {
// Get the actual type arguments of the parameter
Type[] parameters = ((ParameterizedType)genericParameterType).getActualTypeArguments();
Type[] parameters = pt.getActualTypeArguments();
result.addAll(Arrays.asList(parameters));
}
}
@ -338,13 +338,11 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
// Null check
if (instance == null) {
plugin.logError("YAML database request to store a null.");
completableFuture.complete(false);
return completableFuture;
return CompletableFuture.completedFuture(false);
}
if (!(instance instanceof DataObject)) {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
completableFuture.complete(false);
return completableFuture;
return CompletableFuture.completedFuture(false);
}
// This is the Yaml Configuration that will be used and saved at the end
YamlConfiguration config = new YamlConfiguration();
@ -396,9 +394,7 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
handleConfigEntryComments(configEntry, config, yamlComments, parent);
}
if (checkAdapter(field, config, storageLocation, value)) {
continue;
}
if (!checkAdapter(field, config, storageLocation, value)) {
// Set the filename if it has not be set already
if (filename.isEmpty() && method.getName().equals("getUniqueId")) {
// Save the name for when the file is saved
@ -414,6 +410,7 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
config.set(storageLocation, serialize(value));
}
}
}
// If the filename has not been set by now then we have a problem
if (filename.isEmpty()) {
throw new IllegalArgumentException("No uniqueId in class");
@ -469,6 +466,16 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
return id;
}
/**
* Checks if an adapter is to be used. If so, it is used and true returned, if not, fase is returned
* @param field Field
* @param config Yaml Config
* @param storageLocation Storage location
* @param value Value
* @return true if adapater used
* @throws IllegalAccessException exception
* @throws InvocationTargetException exception
*/
private boolean checkAdapter(Field field, YamlConfiguration config, String storageLocation, Object value) throws IllegalAccessException, InvocationTargetException {
Adapter adapterNotation = field.getAnnotation(Adapter.class);
if (adapterNotation != null && AdapterInterface.class.isAssignableFrom(adapterNotation.value())) {

View File

@ -14,7 +14,7 @@ import world.bentobox.bentobox.api.hooks.Hook;
*
* @author Poslovitch
*/
public class MultiverseCoreHook extends Hook {
public class MultiverseCoreHook extends Hook implements WorldManagementHook {
private static final String MULTIVERSE_SET_GENERATOR = "mv modify set generator ";
private static final String MULTIVERSE_IMPORT = "mv import ";
@ -28,6 +28,7 @@ public class MultiverseCoreHook extends Hook {
* @param world - world to register
* @param islandWorld - if true, then this is an island world
*/
@Override
public void registerWorld(World world, boolean islandWorld) {
if (islandWorld) {
// Only register generator if one is defined in the addon (is not null)

View File

@ -0,0 +1,73 @@
package world.bentobox.bentobox.hooks;
import org.bukkit.Material;
import org.bukkit.World;
import com.bergerkiller.bukkit.mw.WorldConfigStore;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.hooks.Hook;
/**
* Provides implementation and interfacing to interact with MyWorlds.
*
* @author bergerkiller (Irmo van den Berge)
*/
public class MyWorldsHook extends Hook implements WorldManagementHook {
public MyWorldsHook() {
super("My_Worlds", Material.FILLED_MAP);
}
/**
* Register the world with MyWorlds
*
* @param world - world to register
* @param islandWorld - if true, then this is an island world
*/
@Override
public void registerWorld(World world, boolean islandWorld) {
if (islandWorld) {
// Only register generator if one is defined in the addon (is not null)
boolean hasGenerator = BentoBox.getInstance().getIWM().getAddon(world).map(gm -> gm.getDefaultWorldGenerator(world.getName(), "") != null).orElse(false);
setUseBentoboxGenerator(world, hasGenerator);
} else {
// Set the generator to null - this will remove any previous registration
setUseBentoboxGenerator(world, false);
}
}
private void setUseBentoboxGenerator(World world, boolean hasGenerator) {
String name = hasGenerator ? BentoBox.getInstance().getName() : null;
try {
WorldConfigStore.get(world).setChunkGeneratorName(name);
// Alternative Reflection way to do it, if a MyWorlds dependency isn't available at
// compile time.
/*
// WorldConfigStore -> public static WorldConfig get(World world);
Object worldConfig = Class.forName("com.bergerkiller.bukkit.mw.WorldConfigStore")
.getMethod("get", World.class)
.invoke(null, world);
// WorldConfig -> public void setChunkGeneratorName(String name);
Class.forName("com.bergerkiller.bukkit.mw.WorldConfig")
.getMethod("setChunkGeneratorName", String.class)
.invoke(worldConfig, name);
*/
} catch (Exception t) {
BentoBox.getInstance().logError("Failed to register world " + world.getName() + " with MyWorlds " + t.getMessage());
}
}
@Override
public boolean hook() {
return true; // The hook process shouldn't fail
}
@Override
public String getFailureCause() {
return null; // The hook process shouldn't fail
}
}

View File

@ -0,0 +1,20 @@
package world.bentobox.bentobox.hooks;
import org.bukkit.World;
/**
* Hook for a type of Multi-World management plugin that must be made
* aware of the correct configuration of a BentoBox World.
*
* @author bergerkiller (Irmo van den Berge)
*/
public interface WorldManagementHook {
/**
* Register the world with the World Management hook
*
* @param world - world to register
* @param islandWorld - if true, then this is an island world
*/
void registerWorld(World world, boolean islandWorld);
}

View File

@ -9,6 +9,7 @@ import java.util.regex.Pattern;
import org.bukkit.entity.Player;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import me.clip.placeholderapi.PlaceholderAPI;
import world.bentobox.bentobox.BentoBox;
@ -69,12 +70,12 @@ public class PlaceholderAPIHook extends PlaceholderHook {
@Override
public void registerPlaceholder(@NonNull Addon addon, @NonNull String placeholder, @NonNull PlaceholderReplacer replacer) {
// Check if the addon expansion does not exist
if (!addonsExpansions.containsKey(addon)) {
addonsExpansions.computeIfAbsent(addon, k -> {
AddonPlaceholderExpansion addonPlaceholderExpansion = new AddonPlaceholderExpansion(addon);
addonPlaceholderExpansion.register();
addonsExpansions.put(addon, addonPlaceholderExpansion);
this.addonPlaceholders.computeIfAbsent(addon, k -> new HashSet<>()).add(placeholder);
}
this.addonPlaceholders.computeIfAbsent(addon, kk -> new HashSet<>()).add(placeholder);
return addonPlaceholderExpansion;
});
addonsExpansions.get(addon).registerPlaceholder(placeholder, replacer);
}
@ -109,7 +110,10 @@ public class PlaceholderAPIHook extends PlaceholderHook {
*/
@Override
@NonNull
public String replacePlaceholders(@NonNull Player player, @NonNull String string) {
public String replacePlaceholders(@Nullable Player player, @NonNull String string) {
if (player == null) {
return PlaceholderAPI.setPlaceholders(player, removeGMPlaceholder(string));
}
// Transform [gamemode] in string to the game mode description name, or remove it for the default replacement
String newString = BentoBox.getInstance().getIWM().getAddon(player.getWorld()).map(gm ->
string.replace(TextVariables.GAMEMODE, gm.getDescription().getName().toLowerCase())

View File

@ -16,6 +16,7 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
@ -49,7 +50,8 @@ public class JoinLeaveListener implements Listener {
User.removePlayer(event.getPlayer());
User user = User.getInstance(event.getPlayer());
if (user == null || user.getUniqueId() == null) {
if (!user.isPlayer() || user.getUniqueId() == null) {
// This should never be the case, but it might be caused by some fake player plugins
return;
}
UUID playerUUID = event.getPlayer().getUniqueId();
@ -142,7 +144,7 @@ public class JoinLeaveListener implements Listener {
}
/**
* This event will clean players inventor
* This event will clean players inventory
* @param event SwitchWorld event.
*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@ -161,8 +163,8 @@ public class JoinLeaveListener implements Listener {
* @param world World where cleaning must occur.
* @param user Targeted user.
*/
private void clearPlayersInventory(World world, @NonNull User user) {
if (user.getUniqueId() == null) return;
private void clearPlayersInventory(@Nullable World world, @NonNull User user) {
if (user.getUniqueId() == null || world == null) return;
// Clear inventory if required
Players playerData = players.getPlayer(user.getUniqueId());

View File

@ -18,7 +18,9 @@ import world.bentobox.bentobox.database.objects.Island;
* Abstracts PlayerPortalEvent and EntityPortalEvent
* @author tastybento
* @deprecated replaced not used in new listeners.
* @since 1.12.1
*/
@Deprecated(since="1.21.0", forRemoval=true)
public class PlayerEntityPortalEvent {
private final EntityPortalEvent epe;

View File

@ -43,8 +43,9 @@ import world.bentobox.bentobox.util.teleport.SafeSpotTeleport;
* @deprecated replaced by better listeners.
* @see world.bentobox.bentobox.listeners.teleports.PlayerTeleportListener
* @see world.bentobox.bentobox.listeners.teleports.EntityTeleportListener
* @since 1.12.1
*/
@Deprecated
@Deprecated(since="1.21.0", forRemoval=true)
public class PortalTeleportationListener implements Listener {
private final BentoBox plugin;
@ -148,7 +149,7 @@ public class PortalTeleportationListener implements Listener {
// Do nothing
}
};
}
}
@ -268,9 +269,8 @@ public class PortalTeleportationListener implements Listener {
return null;
}
Location toLocation = e.getIsland().map(island -> island.getSpawnPoint(env)).
orElse(e.getFrom().toVector().toLocation(toWorld));
Location toLocation = Objects.requireNonNullElse(e.getIsland().map(island -> island.getSpawnPoint(env)).
orElse(e.getFrom().toVector().toLocation(toWorld)), e.getFrom().toVector().toLocation(toWorld));
// Limit Y to the min/max world height.
toLocation.setY(Math.max(Math.min(toLocation.getY(), toWorld.getMaxHeight()), toWorld.getMinHeight()));
@ -438,9 +438,9 @@ public class PortalTeleportationListener implements Listener {
}
}
// From standard nether or end
else if (e.getEntity() instanceof Player){
else if (e.getEntity() instanceof Player player){
e.setCancelled(true);
plugin.getIslands().homeTeleportAsync(overWorld, (Player)e.getEntity());
plugin.getIslands().homeTeleportAsync(overWorld, player);
}
}

View File

@ -4,7 +4,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.World;
@ -39,7 +38,7 @@ public class GeoMobLimitTab implements Tab, ClickHandler {
.filter(EntityType::isAlive)
.filter(t -> !(t.equals(EntityType.PLAYER) || t.equals(EntityType.GIANT) || t.equals(EntityType.ARMOR_STAND)))
.sorted(Comparator.comparing(EntityType::name))
.collect(Collectors.toList()));
.toList());
public enum EntityLimitTabType {
GEO_LIMIT,
@ -110,7 +109,7 @@ public class GeoMobLimitTab implements Tab, ClickHandler {
@Override
public List<@Nullable PanelItem> getPanelItems() {
// Make panel items
return LIVING_ENTITY_TYPES.stream().map(c -> getPanelItem(c, user)).collect(Collectors.toList());
return LIVING_ENTITY_TYPES.stream().map(c -> getPanelItem(c, user)).toList();
}
@Override

View File

@ -1,6 +1,5 @@
package world.bentobox.bentobox.listeners.flags.protection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
@ -36,18 +35,19 @@ public class BlockInteractionListener extends FlagListener
* These cover materials in another server version. This avoids run time errors due to unknown enum values, at the
* expense of a string comparison
*/
private final static Map<String, String> stringFlags;
private static final Map<String, String> stringFlags;
private static final String CHEST = "CHEST";
static
{
stringFlags = Map.of(
"ACACIA_CHEST_BOAT", "CHEST",
"BIRCH_CHEST_BOAT", "CHEST",
"JUNGLE_CHEST_BOAT", "CHEST",
"DARK_OAK_CHEST_BOAT", "CHEST",
"MANGROVE_CHEST_BOAT", "CHEST",
"OAK_CHEST_BOAT", "CHEST",
"SPRUCE_CHEST_BOAT", "CHEST");
"ACACIA_CHEST_BOAT", CHEST,
"BIRCH_CHEST_BOAT", CHEST,
"JUNGLE_CHEST_BOAT", CHEST,
"DARK_OAK_CHEST_BOAT", CHEST,
"MANGROVE_CHEST_BOAT", CHEST,
"OAK_CHEST_BOAT", CHEST,
"SPRUCE_CHEST_BOAT", CHEST);
}
/**

View File

@ -6,7 +6,6 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.entity.AbstractArrow;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.EnderCrystal;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
@ -123,14 +122,12 @@ public class BreakBlocksListener extends FlagListener {
if (e.getDamager() instanceof Player p) {
// Check the break blocks flag
notAllowed(e, p, e.getEntity().getLocation());
} else if (e.getDamager() instanceof Projectile p) {
// Find out who fired the arrow
if (p.getShooter() instanceof Player && notAllowed(e, (Player)p.getShooter(), e.getEntity().getLocation())) {
} else if (e.getDamager() instanceof Projectile p && // Find out who fired the arrow
p.getShooter() instanceof Player player && notAllowed(e, player, e.getEntity().getLocation())) {
e.getEntity().setFireTicks(0);
p.setFireTicks(0);
}
}
}
private boolean notAllowed(EntityDamageByEntityEvent e, Player player, Location location) {
if (!checkIsland(e, player, location, Flags.BREAK_BLOCKS)) return true;

View File

@ -2,7 +2,7 @@ package world.bentobox.bentobox.listeners.flags.protection;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
@ -36,7 +36,7 @@ public class BreedingListener extends FlagListener {
*/
private static final Map<EntityType, List<Material>> BREEDING_ITEMS;
static {
Map<EntityType, List<Material>> bi = new HashMap<>();
Map<EntityType, List<Material>> bi = new EnumMap<>(EntityType.class);
bi.put(EntityType.HORSE, Arrays.asList(Material.GOLDEN_APPLE, Material.GOLDEN_CARROT));
bi.put(EntityType.DONKEY, Arrays.asList(Material.GOLDEN_APPLE, Material.GOLDEN_CARROT));

View File

@ -18,12 +18,11 @@ public class ElytraListener extends FlagListener {
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onGlide(EntityToggleGlideEvent e) {
if (e.getEntity() instanceof Player player) {
if (!checkIsland(e, player, player.getLocation(), Flags.ELYTRA)) {
if (e.getEntity() instanceof Player player
&& !checkIsland(e, player, player.getLocation(), Flags.ELYTRA)) {
player.setGliding(false);
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onGliding(PlayerTeleportEvent e) {

View File

@ -2,8 +2,19 @@ package world.bentobox.bentobox.listeners.flags.protection;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.*;
import org.bukkit.entity.Allay;
import org.bukkit.entity.Animals;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Boat;
import org.bukkit.entity.ChestBoat;
import org.bukkit.entity.Player;
import org.bukkit.entity.Vehicle;
import org.bukkit.entity.Villager;
import org.bukkit.entity.WanderingTrader;
import org.bukkit.entity.minecart.HopperMinecart;
import org.bukkit.entity.minecart.PoweredMinecart;
import org.bukkit.entity.minecart.RideableMinecart;
import org.bukkit.entity.minecart.StorageMinecart;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
@ -11,7 +22,6 @@ import org.bukkit.event.player.PlayerInteractEntityEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.versions.ServerCompatibility;
/**
@ -47,11 +57,19 @@ public class EntityInteractListener extends FlagListener {
// Minecart riding
this.checkIsland(e, p, l, Flags.MINECART);
}
else if (!ServerCompatibility.getInstance().isVersion(
ServerCompatibility.ServerVersion.V1_18,
ServerCompatibility.ServerVersion.V1_18_1,
ServerCompatibility.ServerVersion.V1_18_2) &&
e.getPlayer().isSneaking() && e.getRightClicked() instanceof ChestBoat)
else if (e.getRightClicked() instanceof StorageMinecart)
{
this.checkIsland(e, p, l, Flags.CHEST);
}
else if (e.getRightClicked() instanceof HopperMinecart)
{
this.checkIsland(e, p, l, Flags.HOPPER);
}
else if (e.getRightClicked() instanceof PoweredMinecart)
{
this.checkIsland(e, p, l, Flags.FURNACE);
}
else if (e.getPlayer().isSneaking() && e.getRightClicked() instanceof ChestBoat)
{
// Access to chest boat since 1.19
this.checkIsland(e, p, l, Flags.CHEST);
@ -73,11 +91,7 @@ public class EntityInteractListener extends FlagListener {
this.checkIsland(e, p, l, Flags.NAME_TAG);
}
}
else if (!ServerCompatibility.getInstance().isVersion(
ServerCompatibility.ServerVersion.V1_18,
ServerCompatibility.ServerVersion.V1_18_1,
ServerCompatibility.ServerVersion.V1_18_2) &&
e.getRightClicked() instanceof Allay)
else if (e.getRightClicked() instanceof Allay)
{
// Allay item giving/taking
this.checkIsland(e, p, l, Flags.ALLAY);

View File

@ -74,15 +74,13 @@ public class HurtingListener extends FlagListener {
*/
private void respond(EntityDamageByEntityEvent e, Entity damager, Flag flag) {
// Get the attacker
if (damager instanceof Player) {
checkIsland(e, (Player)damager, damager.getLocation(), flag);
} else if (damager instanceof Projectile p) {
// Find out who fired the projectile
if (p.getShooter() instanceof Player && !checkIsland(e, (Player)p.getShooter(), damager.getLocation(), flag)) {
if (damager instanceof Player player) {
checkIsland(e, player, player.getLocation(), flag);
} else if (damager instanceof Projectile p && // Find out who fired the projectile
p.getShooter() instanceof Player player && !checkIsland(e, player, player.getLocation(), flag)) {
e.getEntity().setFireTicks(0);
}
}
}
/**
* Handle attacks with a fishing rod
@ -166,9 +164,9 @@ public class HurtingListener extends FlagListener {
public void onLingeringPotionSplash(final LingeringPotionSplashEvent e) {
// Try to get the shooter
Projectile projectile = e.getEntity();
if (projectile.getShooter() instanceof Player) {
if (projectile.getShooter() instanceof Player player) {
// Store it and remove it when the effect is gone
thrownPotions.put(e.getAreaEffectCloud().getEntityId(), (Player)projectile.getShooter());
thrownPotions.put(e.getAreaEffectCloud().getEntityId(), player);
getPlugin().getServer().getScheduler().runTaskLater(getPlugin(), () -> thrownPotions.remove(e.getAreaEffectCloud().getEntityId()), e.getAreaEffectCloud().getDuration());
}
}

View File

@ -26,7 +26,6 @@ import org.bukkit.inventory.InventoryHolder;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.versions.ServerCompatibility;
/**
@ -54,10 +53,7 @@ public class InventoryListener extends FlagListener
// Prevent opening animal inventories.
this.checkIsland(event, player, event.getInventory().getLocation(), Flags.MOUNT_INVENTORY);
}
else if (!ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18,
ServerCompatibility.ServerVersion.V1_18_1,
ServerCompatibility.ServerVersion.V1_18_2) &&
inventoryHolder instanceof ChestBoat)
else if (inventoryHolder instanceof ChestBoat)
{
// Prevent opening chest inventories
this.checkIsland(event, player, event.getInventory().getLocation(), Flags.CHEST);
@ -132,8 +128,7 @@ public class InventoryListener extends FlagListener
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST);
}
else if (!ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18, ServerCompatibility.ServerVersion.V1_18_1, ServerCompatibility.ServerVersion.V1_18_2) &&
inventoryHolder instanceof ChestBoat)
else if (inventoryHolder instanceof ChestBoat)
{
// TODO: 1.19 added chest boat. Remove compatibility check when 1.18 is dropped.
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST);

View File

@ -27,8 +27,8 @@ public class ItemDropPickUpListener extends FlagListener {
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPickup(EntityPickupItemEvent e) {
if (e.getEntity() instanceof Player) {
checkIsland(e, (Player)e.getEntity(), e.getItem().getLocation(), Flags.ITEM_PICKUP);
if (e.getEntity() instanceof Player player) {
checkIsland(e, player, e.getItem().getLocation(), Flags.ITEM_PICKUP);
}
}
}

View File

@ -80,7 +80,7 @@ public class LockAndBanListener extends FlagListener {
return;
}
// For each Player in the vehicle
e.getVehicle().getPassengers().stream().filter(en -> en instanceof Player).map(en -> (Player)en).forEach(p -> {
e.getVehicle().getPassengers().stream().filter(Player.class::isInstance).map(Player.class::cast).forEach(p -> {
if (!checkAndNotify(p, e.getTo()).equals(CheckResult.OPEN)) {
p.leaveVehicle();
p.teleport(e.getFrom());
@ -140,13 +140,11 @@ public class LockAndBanListener extends FlagListener {
private CheckResult checkAndNotify(@NonNull Player player, Location loc)
{
CheckResult result = this.check(player, loc);
switch (result)
{
case BANNED -> User.getInstance(player).notify("commands.island.ban.you-are-banned");
case LOCKED -> User.getInstance(player).notify("protection.locked");
if (result == CheckResult.BANNED) {
User.getInstance(player).notify("commands.island.ban.you-are-banned");
} else if (result == CheckResult.LOCKED) {
User.getInstance(player).notify("protection.locked");
}
return result;
}

Some files were not shown because too many files have changed in this diff Show More