diff --git a/.project b/.project new file mode 100644 index 0000000..bac7428 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + pandalib-parent + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/Core/.settings/org.eclipse.jdt.core.prefs b/Core/.settings/org.eclipse.jdt.core.prefs index cf63213..2af1e7b 100644 --- a/Core/.settings/org.eclipse.jdt.core.prefs +++ b/Core/.settings/org.eclipse.jdt.core.prefs @@ -1,6 +1,6 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.compliance=11 org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore diff --git a/Core/src/main/java/fr/pandacube/Pandacube.java b/Core/src/main/java/fr/pandacube/Pandacube.java deleted file mode 100644 index feb690f..0000000 --- a/Core/src/main/java/fr/pandacube/Pandacube.java +++ /dev/null @@ -1,97 +0,0 @@ -package fr.pandacube; - -import java.nio.charset.Charset; -import java.util.Locale; -import java.util.TimeZone; - -import fr.pandacube.util.text_display.Chat; -import net.md_5.bungee.api.ChatColor; - -public class Pandacube { - - public static final Locale LOCALE = Locale.FRANCE; - - public static final TimeZone TIMEZONE = TimeZone.getTimeZone("Europe/Paris"); - - public static final Charset NETWORK_CHARSET = Charset.forName("UTF-8"); - - public static final int NETWORK_TCP_BUFFER_SIZE = 1024 * 1024; - - public static final int NETWORK_TIMEOUT = 0; // no timeout (milli-seconds) - - - - - - //public static final ChatColor CHAT_GREEN_1_NORMAL = ChatColor.of("#5f9765"); // h=126 s=23 l=48 - - public static final ChatColor CHAT_GREEN_1_NORMAL = ChatColor.of("#3db849"); // h=126 s=50 l=48 - public static final ChatColor CHAT_GREEN_2 = ChatColor.of("#5ec969"); // h=126 s=50 l=58 - public static final ChatColor CHAT_GREEN_3 = ChatColor.of("#85d68d"); // h=126 s=50 l=68 - public static final ChatColor CHAT_GREEN_4 = ChatColor.of("#abe3b0"); // h=126 s=50 l=78 - - public static final ChatColor CHAT_GREEN_SATMAX = ChatColor.of("#00ff19"); // h=126 s=100 l=50 - public static final ChatColor CHAT_GREEN_1_SAT = ChatColor.of("#20d532"); // h=126 s=50 l=48 - public static final ChatColor CHAT_GREEN_2_SAT = ChatColor.of("#45e354"); // h=126 s=50 l=58 - public static final ChatColor CHAT_GREEN_3_SAT = ChatColor.of("#71ea7d"); // h=126 s=50 l=68 - public static final ChatColor CHAT_GREEN_4_SAT = ChatColor.of("#9df0a6"); // h=126 s=50 l=78 - - public static final ChatColor CHAT_BROWN_1 = ChatColor.of("#b26d3a"); // h=26 s=51 l=46 - public static final ChatColor CHAT_BROWN_2 = ChatColor.of("#cd9265"); // h=26 s=51 l=60 - public static final ChatColor CHAT_BROWN_3 = ChatColor.of("#e0bb9f"); // h=26 s=51 l=75 - - public static final ChatColor CHAT_BROWN_1_SAT = ChatColor.of("#b35c19"); // h=26 s=75 l=40 - public static final ChatColor CHAT_BROWN_2_SAT = ChatColor.of("#e28136"); // h=26 s=51 l=55 - public static final ChatColor CHAT_BROWN_3_SAT = ChatColor.of("#ecab79"); // h=26 s=51 l=70 - - public static final ChatColor CHAT_GRAY_MID = ChatColor.of("#888888"); - - public static final ChatColor CHAT_BROADCAST_COLOR = ChatColor.YELLOW; - - - public static final ChatColor CHAT_DECORATION_COLOR = CHAT_GREEN_1_NORMAL; - public static final char CHAT_DECORATION_CHAR = '-'; - public static final ChatColor CHAT_URL_COLOR = CHAT_GREEN_1_NORMAL; - public static final ChatColor CHAT_COMMAND_COLOR = CHAT_GRAY_MID; - public static final ChatColor CHAT_COMMAND_HIGHLIGHTED_COLOR = ChatColor.WHITE; - public static final ChatColor CHAT_INFO_COLOR = CHAT_GREEN_4; - public static final ChatColor CHAT_SUCCESS_COLOR = CHAT_GREEN_SATMAX; - public static final ChatColor CHAT_FAILURE_COLOR = ChatColor.of("#ff3333"); - public static final ChatColor CHAT_DATA_COLOR = CHAT_GRAY_MID; - - - public static final ChatColor CHAT_PM_PREFIX_DECORATION = Pandacube.CHAT_BROWN_2_SAT; - public static final ChatColor CHAT_PM_SELF_MESSAGE = Pandacube.CHAT_GREEN_2; - public static final ChatColor CHAT_PM_OTHER_MESSAGE = Pandacube.CHAT_GREEN_4; - - - public static final Chat CHAT_MESSAGE_PREFIX() { - return Chat.text("[") - .color(CHAT_BROADCAST_COLOR) - .thenDecoration("Pandacube") - .thenText("] "); - } - - - - - /** - * Number of decoration character to put between the text and the border of - * the line for left and right aligned text. - */ - public static final int CHAT_NB_CHAR_MARGIN = 1; - - - - - static { - // initialize class to avoid NCDFE when updating the plugin - @SuppressWarnings({ "deprecation", "unused" }) - Class - c1 = fr.pandacube.util.network_api.server.RequestAnalyser.class, - c2 = fr.pandacube.util.network_api.server.RequestAnalyser.BadRequestException.class, - c3 = fr.pandacube.util.network_api.server.Response.class, - c4 = fr.pandacube.util.text_display.ChatUtil.class; - } - -} diff --git a/Core/src/main/java/fr/pandacube/util/text_display/Chat.java b/Core/src/main/java/fr/pandacube/lib/core/chat/Chat.java similarity index 88% rename from Core/src/main/java/fr/pandacube/util/text_display/Chat.java rename to Core/src/main/java/fr/pandacube/lib/core/chat/Chat.java index 2451a8d..8d91f41 100644 --- a/Core/src/main/java/fr/pandacube/util/text_display/Chat.java +++ b/Core/src/main/java/fr/pandacube/lib/core/chat/Chat.java @@ -1,9 +1,9 @@ -package fr.pandacube.util.text_display; +package fr.pandacube.lib.core.chat; import java.awt.Color; import java.util.UUID; +import java.util.function.Supplier; -import fr.pandacube.Pandacube; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ClickEvent; @@ -118,7 +118,7 @@ public abstract class Chat extends ChatStatic { * @return this, for method chaining */ public Chat thenEmptyCharLine() { - return then(ChatUtil.emptyLine(Pandacube.CHAT_DECORATION_CHAR, Pandacube.CHAT_DECORATION_COLOR, console)); + return then(ChatUtil.emptyLine(config.decorationChar, config.decorationColor, console)); } @@ -128,8 +128,8 @@ public abstract class Chat extends ChatStatic { * @return this, for method chaining */ public Chat thenLeftTextCharLine(Chat leftText) { - return then(ChatUtil.leftText(chat().decorationColor().thenText(" ").then(leftText).thenText(" ").get(), Pandacube.CHAT_DECORATION_CHAR, - Pandacube.CHAT_DECORATION_COLOR, Pandacube.CHAT_NB_CHAR_MARGIN, console)); + return then(ChatUtil.leftText(chat().decorationColor().thenText(" ").then(leftText).thenText(" ").get(), config.decorationChar, + config.decorationColor, config.nbCharMargin, console)); } /** * Draws a full line with the default decoration char, colored with the default decoration color, @@ -147,8 +147,8 @@ public abstract class Chat extends ChatStatic { * @return this, for method chaining */ public Chat thenRightTextCharLine(Chat rightText) { - return then(ChatUtil.rightText(chat().decorationColor().thenText(" ").then(rightText).thenText(" ").get(), Pandacube.CHAT_DECORATION_CHAR, - Pandacube.CHAT_DECORATION_COLOR, Pandacube.CHAT_NB_CHAR_MARGIN, console)); + return then(ChatUtil.rightText(chat().decorationColor().thenText(" ").then(rightText).thenText(" ").get(), config.decorationChar, + config.decorationColor, config.nbCharMargin, console)); } /** * Draws a full line with the default decoration char, colored with the default decoration color, @@ -166,8 +166,8 @@ public abstract class Chat extends ChatStatic { * @return this, for method chaining */ public Chat thenCenterTextCharLine(Chat centerText) { - return then(ChatUtil.centerText(chat().decorationColor().thenText(" ").then(centerText).thenText(" ").get(), Pandacube.CHAT_DECORATION_CHAR, - Pandacube.CHAT_DECORATION_COLOR, console)); + return then(ChatUtil.centerText(chat().decorationColor().thenText(" ").then(centerText).thenText(" ").get(), config.decorationChar, + config.decorationColor, console)); } /** * Draws a full line with the default decoration char, colored with the default decoration color, @@ -218,11 +218,11 @@ public abstract class Chat extends ChatStatic { public FormatableChat yellow() { return color(ChatColor.YELLOW); } public FormatableChat white() { return color(ChatColor.WHITE); } - public FormatableChat successColor() { return color(Pandacube.CHAT_SUCCESS_COLOR); } - public FormatableChat failureColor() { return color(Pandacube.CHAT_FAILURE_COLOR); } - public FormatableChat infoColor() { return color(Pandacube.CHAT_INFO_COLOR); } - public FormatableChat dataColor() { return color(Pandacube.CHAT_DATA_COLOR); } - public FormatableChat decorationColor() { return color(Pandacube.CHAT_DECORATION_COLOR); } + public FormatableChat successColor() { return color(config.successColor); } + public FormatableChat failureColor() { return color(config.failureColor); } + public FormatableChat infoColor() { return color(config.infoColor); } + public FormatableChat dataColor() { return color(config.dataColor); } + public FormatableChat decorationColor() { return color(config.decorationColor); } public FormatableChat font(String f) { component.setFont(f); return this; } @@ -306,4 +306,33 @@ public abstract class Chat extends ChatStatic { return values; } + + + + + + protected static final Config config = new Config(); + + public static Config getConfig() { + return config; + } + + public static class Config { + public ChatColor decorationColor = ChatColor.YELLOW; + public char decorationChar = '-'; + public int nbCharMargin = 1; + public ChatColor successColor = ChatColor.GREEN; + public ChatColor failureColor = ChatColor.RED; + public ChatColor infoColor = ChatColor.GOLD; + public ChatColor dataColor = ChatColor.GRAY; + public ChatColor urlColor = ChatColor.GREEN; + public ChatColor commandColor = ChatColor.GRAY; + public ChatColor highlightedCommandColor = ChatColor.WHITE; + public ChatColor broadcastColor = ChatColor.YELLOW; + public Supplier prefix; + + } + + + } diff --git a/Core/src/main/java/fr/pandacube/util/text_display/ChatColorUtil.java b/Core/src/main/java/fr/pandacube/lib/core/chat/ChatColorUtil.java similarity index 99% rename from Core/src/main/java/fr/pandacube/util/text_display/ChatColorUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/chat/ChatColorUtil.java index b10c3bb..99e3e4f 100644 --- a/Core/src/main/java/fr/pandacube/util/text_display/ChatColorUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/chat/ChatColorUtil.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.text_display; +package fr.pandacube.lib.core.chat; import java.awt.Color; import java.util.ArrayList; diff --git a/Core/src/main/java/fr/pandacube/util/text_display/ChatStatic.java b/Core/src/main/java/fr/pandacube/lib/core/chat/ChatStatic.java similarity index 95% rename from Core/src/main/java/fr/pandacube/util/text_display/ChatStatic.java rename to Core/src/main/java/fr/pandacube/lib/core/chat/ChatStatic.java index 3708e6d..e5cee07 100644 --- a/Core/src/main/java/fr/pandacube/util/text_display/ChatStatic.java +++ b/Core/src/main/java/fr/pandacube/lib/core/chat/ChatStatic.java @@ -1,8 +1,8 @@ -package fr.pandacube.util.text_display; +package fr.pandacube.lib.core.chat; import java.util.Objects; -import fr.pandacube.util.text_display.Chat.FormatableChat; +import fr.pandacube.lib.core.chat.Chat.FormatableChat; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.KeybindComponent; import net.md_5.bungee.api.chat.Keybinds; diff --git a/Core/src/main/java/fr/pandacube/util/text_display/ChatUtil.java b/Core/src/main/java/fr/pandacube/lib/core/chat/ChatUtil.java similarity index 96% rename from Core/src/main/java/fr/pandacube/util/text_display/ChatUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/chat/ChatUtil.java index 874d5dc..6265c0b 100644 --- a/Core/src/main/java/fr/pandacube/util/text_display/ChatUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/chat/ChatUtil.java @@ -1,9 +1,9 @@ -package fr.pandacube.util.text_display; +package fr.pandacube.lib.core.chat; -import static fr.pandacube.util.text_display.Chat.chatComponent; -import static fr.pandacube.util.text_display.ChatStatic.chat; -import static fr.pandacube.util.text_display.ChatStatic.legacyText; -import static fr.pandacube.util.text_display.ChatStatic.text; +import static fr.pandacube.lib.core.chat.ChatStatic.chat; +import static fr.pandacube.lib.core.chat.ChatStatic.chatComponent; +import static fr.pandacube.lib.core.chat.ChatStatic.legacyText; +import static fr.pandacube.lib.core.chat.ChatStatic.text; import java.util.ArrayList; import java.util.Arrays; @@ -14,8 +14,7 @@ import java.util.TreeSet; import com.google.common.collect.ImmutableMap; -import fr.pandacube.Pandacube; -import fr.pandacube.util.text_display.Chat.FormatableChat; +import fr.pandacube.lib.core.chat.Chat.FormatableChat; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TextComponent; @@ -60,7 +59,7 @@ public class ChatUtil { String dispURL = (url.length() > 50) ? (url.substring(0, 48) + "...") : url; return chat() .clickURL(url) - .color(Pandacube.CHAT_URL_COLOR) + .color(Chat.getConfig().urlColor) .hoverText( hover != null ? hover : Chat.text(dispURL) ) @@ -85,7 +84,7 @@ public class ChatUtil { /* package */ static BaseComponent createCommandLink(Chat d, String commandWithSlash, Chat hoverText) { FormatableChat c = chat() .clickCommand(commandWithSlash) - .color(Pandacube.CHAT_COMMAND_COLOR); + .color(Chat.getConfig().commandColor); if (hoverText != null) c.hoverText(hoverText); return c.then(d).get(); @@ -110,7 +109,7 @@ public class ChatUtil { /* package */ static BaseComponent createCommandSuggest(Chat d, String commandWithSlash, Chat hoverText) { FormatableChat c = chat() .clickSuggest(commandWithSlash) - .color(Pandacube.CHAT_COMMAND_COLOR); + .color(Chat.getConfig().commandColor); if (hoverText != null) c.hoverText(hoverText); return c.then(d).get(); @@ -163,7 +162,7 @@ public class ChatUtil { FormatableChat pDisp = chatComponent(createCommandLink(Integer.toString(page), String.format(cmdFormat, page), "Aller à la page " + page)); if (page == currentPage) { - pDisp.color(Pandacube.CHAT_COMMAND_HIGHLIGHTED_COLOR); + pDisp.color(Chat.getConfig().highlightedCommandColor); } d.then(pDisp); diff --git a/Core/src/main/java/fr/pandacube/util/text_display/TextProgressBar.java b/Core/src/main/java/fr/pandacube/lib/core/chat/TextProgressBar.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/text_display/TextProgressBar.java rename to Core/src/main/java/fr/pandacube/lib/core/chat/TextProgressBar.java index c5e3b9c..51b929c 100644 --- a/Core/src/main/java/fr/pandacube/util/text_display/TextProgressBar.java +++ b/Core/src/main/java/fr/pandacube/lib/core/chat/TextProgressBar.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.text_display; +package fr.pandacube.lib.core.chat; import net.md_5.bungee.api.ChatColor; diff --git a/Core/src/main/java/fr/pandacube/util/commands/AbstractCommand.java b/Core/src/main/java/fr/pandacube/lib/core/commands/AbstractCommand.java similarity index 92% rename from Core/src/main/java/fr/pandacube/util/commands/AbstractCommand.java rename to Core/src/main/java/fr/pandacube/lib/core/commands/AbstractCommand.java index a208973..da80c7f 100644 --- a/Core/src/main/java/fr/pandacube/util/commands/AbstractCommand.java +++ b/Core/src/main/java/fr/pandacube/lib/core/commands/AbstractCommand.java @@ -1,8 +1,8 @@ -package fr.pandacube.util.commands; +package fr.pandacube.lib.core.commands; import java.util.Arrays; -import fr.pandacube.util.text_display.ChatStatic; +import fr.pandacube.lib.core.chat.ChatStatic; public class AbstractCommand extends ChatStatic { diff --git a/Core/src/main/java/fr/pandacube/util/commands/BadCommandUsage.java b/Core/src/main/java/fr/pandacube/lib/core/commands/BadCommandUsage.java similarity index 95% rename from Core/src/main/java/fr/pandacube/util/commands/BadCommandUsage.java rename to Core/src/main/java/fr/pandacube/lib/core/commands/BadCommandUsage.java index 2db96a7..888d290 100644 --- a/Core/src/main/java/fr/pandacube/util/commands/BadCommandUsage.java +++ b/Core/src/main/java/fr/pandacube/lib/core/commands/BadCommandUsage.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.commands; +package fr.pandacube.lib.core.commands; import java.util.logging.Logger; diff --git a/Core/src/main/java/fr/pandacube/util/commands/SuggestionsSupplier.java b/Core/src/main/java/fr/pandacube/lib/core/commands/SuggestionsSupplier.java similarity index 99% rename from Core/src/main/java/fr/pandacube/util/commands/SuggestionsSupplier.java rename to Core/src/main/java/fr/pandacube/lib/core/commands/SuggestionsSupplier.java index 1cf7846..93eb5cc 100644 --- a/Core/src/main/java/fr/pandacube/util/commands/SuggestionsSupplier.java +++ b/Core/src/main/java/fr/pandacube/lib/core/commands/SuggestionsSupplier.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.commands; +package fr.pandacube.lib.core.commands; import java.util.ArrayList; import java.util.Arrays; @@ -10,7 +10,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; -import fr.pandacube.util.ListUtil; +import fr.pandacube.lib.core.util.ListUtil; @FunctionalInterface public interface SuggestionsSupplier { diff --git a/Core/src/main/java/fr/pandacube/util/config/AbstractConfig.java b/Core/src/main/java/fr/pandacube/lib/core/config/AbstractConfig.java similarity index 96% rename from Core/src/main/java/fr/pandacube/util/config/AbstractConfig.java rename to Core/src/main/java/fr/pandacube/lib/core/config/AbstractConfig.java index 20b2534..0bcbacd 100644 --- a/Core/src/main/java/fr/pandacube/util/config/AbstractConfig.java +++ b/Core/src/main/java/fr/pandacube/lib/core/config/AbstractConfig.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.config; +package fr.pandacube.lib.core.config; import java.io.BufferedReader; import java.io.File; @@ -9,8 +9,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import fr.pandacube.util.Log; -import fr.pandacube.util.text_display.ChatColorUtil; +import fr.pandacube.lib.core.chat.ChatColorUtil; +import fr.pandacube.lib.core.util.Log; /** * Classe chargeant en mémoire un fichier de configuration ou un dossier donné * @author Marc Baloup diff --git a/Core/src/main/java/fr/pandacube/util/config/AbstractConfigManager.java b/Core/src/main/java/fr/pandacube/lib/core/config/AbstractConfigManager.java similarity index 94% rename from Core/src/main/java/fr/pandacube/util/config/AbstractConfigManager.java rename to Core/src/main/java/fr/pandacube/lib/core/config/AbstractConfigManager.java index 066bd54..bdf138d 100644 --- a/Core/src/main/java/fr/pandacube/util/config/AbstractConfigManager.java +++ b/Core/src/main/java/fr/pandacube/lib/core/config/AbstractConfigManager.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.config; +package fr.pandacube.lib.core.config; import java.io.File; import java.io.IOException; diff --git a/Core/src/main/java/fr/pandacube/util/orm/ORM.java b/Core/src/main/java/fr/pandacube/lib/core/db/DB.java similarity index 79% rename from Core/src/main/java/fr/pandacube/util/orm/ORM.java rename to Core/src/main/java/fr/pandacube/lib/core/db/DB.java index 7121d15..b36c202 100644 --- a/Core/src/main/java/fr/pandacube/util/orm/ORM.java +++ b/Core/src/main/java/fr/pandacube/lib/core/db/DB.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.orm; +package fr.pandacube.lib.core.db; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -10,54 +10,61 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; import org.javatuples.Pair; -import fr.pandacube.util.Log; +import fr.pandacube.lib.core.util.Log; /** - * ORM = Object-Relational Mapping + * Static class to handle most of the database operations. + * + * To use this database library, first call {@link #init(DBConnection)} with an appropriate {@link DBConnection}, + * they you can initialize every table you need for your application, using {@link #initTable(Class)}. * * @author Marc Baloup - * */ -public final class ORM { +public final class DB { private static List>> tables = new ArrayList<>(); private static Map>, String> tableNames = new HashMap<>(); private static DBConnection connection; + /* package */ static String tablePrefix = ""; public static DBConnection getConnection() { return connection; } - public synchronized static > void init(DBConnection conn) { - + public synchronized static > void init(DBConnection conn, String tablePrefix) { connection = conn; - - + DB.tablePrefix = Objects.requireNonNull(tablePrefix); } - public static synchronized > void initTable(Class elemClass) throws ORMInitTableException { + public static synchronized > void initTable(Class elemClass) throws DBInitTableException { + if (connection == null) { + throw new DBInitTableException(elemClass, "Database connection is not yet initialized."); + } if (tables.contains(elemClass)) return; try { tables.add(elemClass); - Log.debug("[ORM] Start Init SQL table "+elemClass.getSimpleName()); + Log.debug("[DB] Start Init SQL table "+elemClass.getSimpleName()); E instance = elemClass.getConstructor().newInstance(); - String tableName = instance.tableName(); + String tableName = tablePrefix + instance.tableName(); tableNames.put(elemClass, tableName); if (!tableExistInDB(tableName)) createTable(instance); - Log.debug("[ORM] End init SQL table "+elemClass.getSimpleName()); + Log.debug("[DB] End init SQL table "+elemClass.getSimpleName()); } catch (Exception|ExceptionInInitializerError e) { - throw new ORMInitTableException(elemClass, e); + throw new DBInitTableException(elemClass, e); } } private static > void createTable(E elem) throws SQLException { + + String tableName = tablePrefix + elem.tableName(); - String sql = "CREATE TABLE IF NOT EXISTS " + elem.tableName() + " ("; + String sql = "CREATE TABLE IF NOT EXISTS " + tableName + " ("; List params = new ArrayList<>(); Collection> tableFields = elem.getFields().values(); @@ -82,7 +89,7 @@ public final class ORM { } } - public static > String getTableName(Class elemClass) throws ORMException { + public static > String getTableName(Class elemClass) throws DBException { initTable(elemClass); return tableNames.get(elemClass); } @@ -97,91 +104,91 @@ public final class ORM { @SuppressWarnings("unchecked") public static > SQLField getSQLIdField(Class elemClass) - throws ORMInitTableException { + throws DBInitTableException { initTable(elemClass); return (SQLField) SQLElement.fieldsCache.get(elemClass).get("id"); } - public static > SQLElementList getByIds(Class elemClass, Integer... ids) throws ORMException { + public static > SQLElementList getByIds(Class elemClass, Integer... ids) throws DBException { return getByIds(elemClass, Arrays.asList(ids)); } public static > SQLElementList getByIds(Class elemClass, Collection ids) - throws ORMException { + throws DBException { return getAll(elemClass, getSQLIdField(elemClass).in(ids), SQLOrderBy.asc(getSQLIdField(elemClass)), 1, null); } - public static > E getById(Class elemClass, int id) throws ORMException { + public static > E getById(Class elemClass, int id) throws DBException { return getFirst(elemClass, getSQLIdField(elemClass).eq(id)); } public static > E getFirst(Class elemClass, SQLWhere where) - throws ORMException { + throws DBException { return getFirst(elemClass, where, null, null); } public static > E getFirst(Class elemClass, SQLOrderBy orderBy) - throws ORMException { + throws DBException { return getFirst(elemClass, null, orderBy, null); } public static > E getFirst(Class elemClass, SQLWhere where, SQLOrderBy orderBy) - throws ORMException { + throws DBException { return getFirst(elemClass, where, orderBy, null); } public static > E getFirst(Class elemClass, SQLWhere where, SQLOrderBy orderBy, Integer offset) - throws ORMException { + throws DBException { SQLElementList elts = getAll(elemClass, where, orderBy, 1, offset); return (elts.size() == 0) ? null : elts.get(0); } - public static > SQLElementList getAll(Class elemClass) throws ORMException { + public static > SQLElementList getAll(Class elemClass) throws DBException { return getAll(elemClass, null, null, null, null); } - public static > SQLElementList getAll(Class elemClass, SQLWhere where) throws ORMException { + public static > SQLElementList getAll(Class elemClass, SQLWhere where) throws DBException { return getAll(elemClass, where, null, null, null); } public static > SQLElementList getAll(Class elemClass, SQLWhere where, - SQLOrderBy orderBy) throws ORMException { + SQLOrderBy orderBy) throws DBException { return getAll(elemClass, where, orderBy, null, null); } public static > SQLElementList getAll(Class elemClass, SQLWhere where, - SQLOrderBy orderBy, Integer limit) throws ORMException { + SQLOrderBy orderBy, Integer limit) throws DBException { return getAll(elemClass, where, orderBy, limit, null); } public static > SQLElementList getAll(Class elemClass, SQLWhere where, - SQLOrderBy orderBy, Integer limit, Integer offset) throws ORMException { + SQLOrderBy orderBy, Integer limit, Integer offset) throws DBException { SQLElementList elmts = new SQLElementList<>(); forEach(elemClass, where, orderBy, limit, offset, elmts::add); return elmts; } - public static > void forEach(Class elemClass, Consumer action) throws ORMException { + public static > void forEach(Class elemClass, Consumer action) throws DBException { forEach(elemClass, null, null, null, null, action); } public static > void forEach(Class elemClass, SQLWhere where, - Consumer action) throws ORMException { + Consumer action) throws DBException { forEach(elemClass, where, null, null, null, action); } public static > void forEach(Class elemClass, SQLWhere where, - SQLOrderBy orderBy, Consumer action) throws ORMException { + SQLOrderBy orderBy, Consumer action) throws DBException { forEach(elemClass, where, orderBy, null, null, action); } public static > void forEach(Class elemClass, SQLWhere where, - SQLOrderBy orderBy, Integer limit, Consumer action) throws ORMException { + SQLOrderBy orderBy, Integer limit, Consumer action) throws DBException { forEach(elemClass, where, orderBy, limit, null, action); } public static > void forEach(Class elemClass, SQLWhere where, - SQLOrderBy orderBy, Integer limit, Integer offset, Consumer action) throws ORMException { + SQLOrderBy orderBy, Integer limit, Integer offset, Consumer action) throws DBException { initTable(elemClass); try { @@ -206,18 +213,18 @@ public final class ORM { } } } catch (SQLException e) { - throw new ORMException(e); + throw new DBException(e); } } - public static > long count(Class elemClass) throws ORMException { + public static > long count(Class elemClass) throws DBException { return count(elemClass, null); } - public static > long count(Class elemClass, SQLWhere where) throws ORMException { + public static > long count(Class elemClass, SQLWhere where) throws DBException { initTable(elemClass); try { @@ -238,17 +245,17 @@ public final class ORM { } } } catch (SQLException e) { - throw new ORMException(e); + throw new DBException(e); } - throw new ORMException("Can’t retrieve element count from database (The ResultSet may be empty)"); + throw new DBException("Can’t retrieve element count from database (The ResultSet may be empty)"); } - public static ResultSet customQueryStatement(String sql, List params) throws ORMException { + public static ResultSet customQueryStatement(String sql, List params) throws DBException { try { PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql); int i = 1; @@ -264,7 +271,7 @@ public final class ORM { return rs; } catch (SQLException e) { - throw new ORMException(e); + throw new DBException(e); } } @@ -272,11 +279,11 @@ public final class ORM { - public static > SQLUpdate update(Class elemClass, SQLWhere where) throws ORMException { + public static > SQLUpdate update(Class elemClass, SQLWhere where) throws DBException { return new SQLUpdate<>(elemClass, where); } - /* package */ static > int update(Class elemClass, SQLWhere where, Map, Object> values) throws ORMException { + /* package */ static > int update(Class elemClass, SQLWhere where, Map, Object> values) throws DBException { return new SQLUpdate<>(elemClass, where, values).execute(); } @@ -286,9 +293,9 @@ public final class ORM { * @param elemClass the SQLElement representing the table. * @param where the condition to meet for an element to be deleted from the table. If null, the table is truncated using {@link #truncateTable(Class)}. * @return The return value of {@link PreparedStatement#executeUpdate()}, for an SQL query {@code DELETE}. - * @throws ORMException + * @throws DBException */ - public static > int delete(Class elemClass, SQLWhere where) throws ORMException { + public static > int delete(Class elemClass, SQLWhere where) throws DBException { initTable(elemClass); if (where == null) { @@ -308,7 +315,7 @@ public final class ORM { - public static int customUpdateStatement(String sql, List params) throws ORMException { + public static int customUpdateStatement(String sql, List params) throws DBException { try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql)) { int i = 1; @@ -320,22 +327,22 @@ public final class ORM { return ps.executeUpdate(); } catch (SQLException e) { - throw new ORMException(e); + throw new DBException(e); } } - public static > int truncateTable(Class elemClass) throws ORMException { + public static > int truncateTable(Class elemClass) throws DBException { try (Statement stmt = connection.getNativeConnection().createStatement()) { return stmt.executeUpdate("TRUNCATE `" + getTableName(elemClass) + "`"); } catch(SQLException e) { - throw new ORMException(e); + throw new DBException(e); } } @SuppressWarnings("unchecked") - private static > E getElementInstance(ResultSet set, Class elemClass) throws ORMException { + private static > E getElementInstance(ResultSet set, Class elemClass) throws DBException { try { E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id")); @@ -363,7 +370,7 @@ public final class ORM { try { val = ((SQLCustomType)sqlField.type).dbToJavaConv.apply(val); } catch (Exception e) { - throw new ORMException("Error while converting value of field '"+sqlField.getName()+"' with SQLCustomType from "+((SQLCustomType)sqlField.type).intermediateJavaType + throw new DBException("Error while converting value of field '"+sqlField.getName()+"' with SQLCustomType from "+((SQLCustomType)sqlField.type).intermediateJavaType +"(jdbc source) to "+sqlField.type.getJavaType()+"(java destination). The original value is '"+val.toString()+"'", e); } } @@ -377,15 +384,15 @@ public final class ORM { } } - if (!instance.isValidForSave()) throw new ORMException( + if (!instance.isValidForSave()) throw new DBException( "This SQLElement representing a database entry is not valid for save : " + instance.toString()); return instance; } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | SQLException e) { - throw new ORMException("Can't instanciate " + elemClass.getName(), e); + throw new DBException("Can't instanciate " + elemClass.getName(), e); } } - private ORM() {} // rend la classe non instanciable + private DB() {} } diff --git a/Core/src/main/java/fr/pandacube/util/orm/DBConnection.java b/Core/src/main/java/fr/pandacube/lib/core/db/DBConnection.java similarity index 96% rename from Core/src/main/java/fr/pandacube/util/orm/DBConnection.java rename to Core/src/main/java/fr/pandacube/lib/core/db/DBConnection.java index 49f8dc3..aa2d637 100644 --- a/Core/src/main/java/fr/pandacube/util/orm/DBConnection.java +++ b/Core/src/main/java/fr/pandacube/lib/core/db/DBConnection.java @@ -1,11 +1,11 @@ -package fr.pandacube.util.orm; +package fr.pandacube.lib.core.db; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; -import fr.pandacube.util.Log; +import fr.pandacube.lib.core.util.Log; public class DBConnection { private static final long CONNECTION_CHECK_TIMEOUT = 30000; // in ms diff --git a/Core/src/main/java/fr/pandacube/lib/core/db/DBException.java b/Core/src/main/java/fr/pandacube/lib/core/db/DBException.java new file mode 100644 index 0000000..b536da1 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/db/DBException.java @@ -0,0 +1,18 @@ +package fr.pandacube.lib.core.db; + +public class DBException extends Exception { + private static final long serialVersionUID = 1L; + + public DBException(Throwable initCause) { + super(initCause); + } + + public DBException(String message, Throwable initCause) { + super(message, initCause); + } + + public DBException(String message) { + super(message); + } + +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/db/DBInitTableException.java b/Core/src/main/java/fr/pandacube/lib/core/db/DBInitTableException.java new file mode 100644 index 0000000..18fcb61 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/db/DBInitTableException.java @@ -0,0 +1,18 @@ +package fr.pandacube.lib.core.db; + +public class DBInitTableException extends DBException { + private static final long serialVersionUID = 1L; + + /* package */ > DBInitTableException(Class tableElem) { + super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null")); + } + + /* package */ > DBInitTableException(Class tableElem, Throwable t) { + super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"), t); + } + + /* package */ > DBInitTableException(Class tableElem, String message) { + super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null") + ": " + message); + } + +} diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLCustomType.java b/Core/src/main/java/fr/pandacube/lib/core/db/SQLCustomType.java similarity index 96% rename from Core/src/main/java/fr/pandacube/util/orm/SQLCustomType.java rename to Core/src/main/java/fr/pandacube/lib/core/db/SQLCustomType.java index 34bd9ec..a0d4d52 100644 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLCustomType.java +++ b/Core/src/main/java/fr/pandacube/lib/core/db/SQLCustomType.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.orm; +package fr.pandacube.lib.core.db; import java.util.function.Function; diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLElement.java b/Core/src/main/java/fr/pandacube/lib/core/db/SQLElement.java similarity index 92% rename from Core/src/main/java/fr/pandacube/util/orm/SQLElement.java rename to Core/src/main/java/fr/pandacube/lib/core/db/SQLElement.java index 775427a..2cd569b 100644 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLElement.java +++ b/Core/src/main/java/fr/pandacube/lib/core/db/SQLElement.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.orm; +package fr.pandacube.lib.core.db; import java.lang.reflect.Modifier; import java.sql.Date; @@ -22,14 +22,14 @@ import org.apache.commons.lang.builder.ToStringBuilder; import com.google.gson.Gson; import com.google.gson.JsonObject; -import fr.pandacube.util.EnumUtil; -import fr.pandacube.util.Log; +import fr.pandacube.lib.core.util.EnumUtil; +import fr.pandacube.lib.core.util.Log; public abstract class SQLElement> { /** cache for fields for each subclass of SQLElement */ /* package */ static final Map>, SQLFieldMap>> fieldsCache = new HashMap<>(); - DBConnection db = ORM.getConnection(); + DBConnection db = DB.getConnection(); private boolean stored = false; private int id; @@ -43,8 +43,8 @@ public abstract class SQLElement> { public SQLElement() { try { - ORM.initTable((Class)getClass()); - } catch (ORMInitTableException e) { + DB.initTable((Class)getClass()); + } catch (DBInitTableException e) { throw new RuntimeException(e); } @@ -79,7 +79,7 @@ public abstract class SQLElement> { } /** - * @return The name of the table in the database. + * @return The name of the table in the database, without the prefix defined by {@link DB#init(DBConnection, String)}. */ protected abstract String tableName(); @@ -191,10 +191,10 @@ public abstract class SQLElement> { * @param

the table class of the primary key targeted by the specified foreign key field * @return the element in the table P that his primary key correspond to the foreign key value of this element. */ - public > P getReferencedEntry(SQLFKField field) throws ORMException { + public > P getReferencedEntry(SQLFKField field) throws DBException { T fkValue = get(field); if (fkValue == null) return null; - return ORM.getFirst(field.getForeignElementClass(), field.getPrimaryField().eq(fkValue), null); + return DB.getFirst(field.getForeignElementClass(), field.getPrimaryField().eq(fkValue), null); } /** @@ -202,10 +202,10 @@ public abstract class SQLElement> { * @param the table class of the foreign key that reference a primary key of this element. * @return all elements in the table F for which the specified foreign key value correspond to the primary key of this element. */ - public > SQLElementList getReferencingForeignEntries(SQLFKField field, SQLOrderBy orderBy, Integer limit, Integer offset) throws ORMException { + public > SQLElementList getReferencingForeignEntries(SQLFKField field, SQLOrderBy orderBy, Integer limit, Integer offset) throws DBException { T value = get(field.getPrimaryField()); if (value == null) return new SQLElementList<>(); - return ORM.getAll(field.getSQLElementType(), field.eq(value), orderBy, limit, offset); + return DB.getAll(field.getSQLElementType(), field.eq(value), orderBy, limit, offset); } public boolean isValidForSave() { @@ -225,11 +225,11 @@ public abstract class SQLElement> { } @SuppressWarnings("unchecked") - public E save() throws ORMException { + public E save() throws DBException { if (!isValidForSave()) throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved."); - ORM.initTable((Class)getClass()); + DB.initTable((Class)getClass()); try { if (stored) { // mettre à jour les valeurs dans la base @@ -243,7 +243,7 @@ public abstract class SQLElement> { if (modifiedValues.isEmpty()) return (E) this; - ORM.update((Class)getClass(), getFieldId().eq(getId()), modifiedValues); + DB.update((Class)getClass(), getFieldId().eq(getId()), modifiedValues); } else { // ajouter dans la base @@ -268,7 +268,7 @@ public abstract class SQLElement> { } try (PreparedStatement ps = db.getNativeConnection().prepareStatement( - "INSERT INTO " + tableName() + " (" + concat_fields + ") VALUES (" + concat_vals + ")", + "INSERT INTO " + DB.tablePrefix + tableName() + " (" + concat_fields + ") VALUES (" + concat_vals + ")", Statement.RETURN_GENERATED_KEYS)) { int i = 1; @@ -287,19 +287,19 @@ public abstract class SQLElement> { modifiedSinceLastSave.clear(); } catch (SQLException e) { - throw new ORMException("Error while saving data", e); + throw new DBException("Error while saving data", e); } return (E) this; } @SuppressWarnings({ "rawtypes", "unchecked" }) - protected static > void addValueToSQLObjectList(List list, SQLField field, Object jValue) throws ORMException { + protected static > void addValueToSQLObjectList(List list, SQLField field, Object jValue) throws DBException { if (jValue != null && field.type instanceof SQLCustomType) { try { jValue = ((SQLCustomType)field.type).javaToDbConv.apply(jValue); } catch (Exception e) { - throw new ORMException("Error while converting value of field '"+field.getName()+"' with SQLCustomType from "+field.type.getJavaType() + throw new DBException("Error while converting value of field '"+field.getName()+"' with SQLCustomType from "+field.type.getJavaType() +"(java source) to "+((SQLCustomType)field.type).intermediateJavaType+"(jdbc destination). The original value is '"+jValue.toString()+"'", e); } } @@ -319,16 +319,16 @@ public abstract class SQLElement> { return (SQLField) fields.get("id"); } - public void delete() throws ORMException { + public void delete() throws DBException { if (stored) { // supprimer la ligne de la base try (PreparedStatement st = db.getNativeConnection() - .prepareStatement("DELETE FROM " + tableName() + " WHERE id=" + id)) { + .prepareStatement("DELETE FROM " + DB.tablePrefix + tableName() + " WHERE id=" + id)) { Log.debug(st.toString()); st.executeUpdate(); markAsNotStored(); } catch (SQLException e) { - throw new ORMException(e); + throw new DBException(e); } } diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLElementList.java b/Core/src/main/java/fr/pandacube/lib/core/db/SQLElementList.java similarity index 89% rename from Core/src/main/java/fr/pandacube/util/orm/SQLElementList.java rename to Core/src/main/java/fr/pandacube/lib/core/db/SQLElementList.java index b2eeb53..0d32a9c 100644 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLElementList.java +++ b/Core/src/main/java/fr/pandacube/lib/core/db/SQLElementList.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.orm; +package fr.pandacube.lib.core.db; import java.sql.SQLException; import java.util.ArrayList; @@ -12,7 +12,7 @@ import java.util.stream.Collectors; import com.google.gson.JsonArray; -import fr.pandacube.util.Log; +import fr.pandacube.lib.core.util.Log; /** * @@ -75,14 +75,14 @@ public class SQLElementList> extends ArrayList { * * @throws SQLException */ - public synchronized int saveCommon() throws ORMException { + public synchronized int saveCommon() throws DBException { List storedEl = getStoredEl(); if (storedEl.isEmpty()) return 0; @SuppressWarnings("unchecked") Class classEl = (Class)storedEl.get(0).getClass(); - int ret = ORM.update(classEl, + int ret = DB.update(classEl, storedEl.get(0).getFieldId().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList()) ), modifiedValues); @@ -106,7 +106,7 @@ public class SQLElementList> extends ArrayList { } /** - * @deprecated please use {@link ORM#delete(Class, SQLWhere)} instead, + * @deprecated please use {@link DB#delete(Class, SQLWhere)} instead, * except if you really want to fetch the data before removing them from database. */ @Deprecated @@ -118,12 +118,12 @@ public class SQLElementList> extends ArrayList { @SuppressWarnings("unchecked") Class classEl = (Class)storedEl.get(0).getClass(); - ORM.delete(classEl, + DB.delete(classEl, storedEl.get(0).getFieldId().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList())) ); for (E el : storedEl) el.markAsNotStored(); - } catch (ORMException e) { + } catch (DBException e) { Log.severe(e); } @@ -131,7 +131,7 @@ public class SQLElementList> extends ArrayList { - public > SQLElementList

getReferencedEntries(SQLFKField foreignKey, SQLOrderBy

orderBy) throws ORMException { + public > SQLElementList

getReferencedEntries(SQLFKField foreignKey, SQLOrderBy

orderBy) throws DBException { Set values = new HashSet<>(); forEach(v -> { T val = v.get(foreignKey); @@ -143,12 +143,12 @@ public class SQLElementList> extends ArrayList { return new SQLElementList<>(); } - return ORM.getAll(foreignKey.getForeignElementClass(), foreignKey.getPrimaryField().in(values), orderBy, null, null); + return DB.getAll(foreignKey.getForeignElementClass(), foreignKey.getPrimaryField().in(values), orderBy, null, null); } - public > Map getReferencedEntriesInGroups(SQLFKField foreignKey) throws ORMException { + public > Map getReferencedEntriesInGroups(SQLFKField foreignKey) throws DBException { SQLElementList

foreignElemts = getReferencedEntries(foreignKey, null); Map ret = new HashMap<>(); @@ -158,7 +158,7 @@ public class SQLElementList> extends ArrayList { - public > SQLElementList getReferencingForeignEntries(SQLFKField foreignKey, SQLOrderBy orderBy, Integer limit, Integer offset) throws ORMException { + public > SQLElementList getReferencingForeignEntries(SQLFKField foreignKey, SQLOrderBy orderBy, Integer limit, Integer offset) throws DBException { Set values = new HashSet<>(); forEach(v -> { T val = v.get(foreignKey.getPrimaryField()); @@ -170,12 +170,12 @@ public class SQLElementList> extends ArrayList { return new SQLElementList<>(); } - return ORM.getAll(foreignKey.getSQLElementType(), foreignKey.in(values), orderBy, limit, offset); + return DB.getAll(foreignKey.getSQLElementType(), foreignKey.in(values), orderBy, limit, offset); } - public > Map> getReferencingForeignEntriesInGroups(SQLFKField foreignKey, SQLOrderBy orderBy, Integer limit, Integer offset) throws ORMException { + public > Map> getReferencingForeignEntriesInGroups(SQLFKField foreignKey, SQLOrderBy orderBy, Integer limit, Integer offset) throws DBException { SQLElementList foreignElements = getReferencingForeignEntries(foreignKey, orderBy, limit, offset); Map> map = new HashMap<>(); diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLFKField.java b/Core/src/main/java/fr/pandacube/lib/core/db/SQLFKField.java similarity index 91% rename from Core/src/main/java/fr/pandacube/util/orm/SQLFKField.java rename to Core/src/main/java/fr/pandacube/lib/core/db/SQLFKField.java index d285f64..a0c4c98 100644 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLFKField.java +++ b/Core/src/main/java/fr/pandacube/lib/core/db/SQLFKField.java @@ -1,6 +1,6 @@ -package fr.pandacube.util.orm; +package fr.pandacube.lib.core.db; -import fr.pandacube.util.Log; +import fr.pandacube.lib.core.util.Log; /** * @@ -27,9 +27,9 @@ public class SQLFKField, T, P extends SQLElement

> ext /* package */ static , F extends SQLElement> SQLFKField idFK(boolean nul, Integer deflt, Class fkEl) { if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null"); try { - SQLField f = ORM.getSQLIdField(fkEl); + SQLField f = DB.getSQLIdField(fkEl); return new SQLFKField<>(f.type, nul, deflt, fkEl, f); - } catch (ORMInitTableException e) { + } catch (DBInitTableException e) { Log.severe("Can't create Foreign key Field targetting id field of '"+fkEl+"'", e); return null; } @@ -47,8 +47,8 @@ public class SQLFKField, T, P extends SQLElement

> ext private void construct(Class

fkEl, SQLField fkF) { if (fkF == null) throw new IllegalArgumentException("foreignKeyField can't be null"); try { - ORM.initTable(fkEl); - } catch (ORMInitTableException e) { + DB.initTable(fkEl); + } catch (DBInitTableException e) { throw new RuntimeException(e); } diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLField.java b/Core/src/main/java/fr/pandacube/lib/core/db/SQLField.java similarity index 91% rename from Core/src/main/java/fr/pandacube/util/orm/SQLField.java rename to Core/src/main/java/fr/pandacube/lib/core/db/SQLField.java index 5448be2..3f0113a 100644 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLField.java +++ b/Core/src/main/java/fr/pandacube/lib/core/db/SQLField.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.orm; +package fr.pandacube.lib.core.db; import java.util.ArrayList; import java.util.Collection; @@ -6,7 +6,11 @@ import java.util.List; import org.javatuples.Pair; -import fr.pandacube.util.orm.SQLWhereComp.SQLComparator; +import fr.pandacube.lib.core.db.SQLWhere.SQLWhereComp; +import fr.pandacube.lib.core.db.SQLWhere.SQLWhereIn; +import fr.pandacube.lib.core.db.SQLWhere.SQLWhereLike; +import fr.pandacube.lib.core.db.SQLWhere.SQLWhereNull; +import fr.pandacube.lib.core.db.SQLWhere.SQLWhereComp.SQLComparator; public class SQLField, T> { diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLOrderBy.java b/Core/src/main/java/fr/pandacube/lib/core/db/SQLOrderBy.java similarity index 98% rename from Core/src/main/java/fr/pandacube/util/orm/SQLOrderBy.java rename to Core/src/main/java/fr/pandacube/lib/core/db/SQLOrderBy.java index 9534da0..3f1919d 100644 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLOrderBy.java +++ b/Core/src/main/java/fr/pandacube/lib/core/db/SQLOrderBy.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.orm; +package fr.pandacube.lib.core.db; import java.util.ArrayList; import java.util.List; diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLType.java b/Core/src/main/java/fr/pandacube/lib/core/db/SQLType.java similarity index 95% rename from Core/src/main/java/fr/pandacube/util/orm/SQLType.java rename to Core/src/main/java/fr/pandacube/lib/core/db/SQLType.java index f1dc055..8860a3a 100644 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLType.java +++ b/Core/src/main/java/fr/pandacube/lib/core/db/SQLType.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.orm; +package fr.pandacube.lib.core.db; public class SQLType { diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLUpdate.java b/Core/src/main/java/fr/pandacube/lib/core/db/SQLUpdate.java similarity index 80% rename from Core/src/main/java/fr/pandacube/util/orm/SQLUpdate.java rename to Core/src/main/java/fr/pandacube/lib/core/db/SQLUpdate.java index b1922ec..fb4a416 100644 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLUpdate.java +++ b/Core/src/main/java/fr/pandacube/lib/core/db/SQLUpdate.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.orm; +package fr.pandacube.lib.core.db; import java.util.ArrayList; import java.util.HashMap; @@ -7,7 +7,7 @@ import java.util.Map; import org.javatuples.Pair; -import fr.pandacube.util.Log; +import fr.pandacube.lib.core.util.Log; public class SQLUpdate> { @@ -37,14 +37,14 @@ public class SQLUpdate> { return this; } - public int execute() throws ORMException { + public int execute() throws DBException { if (values.isEmpty()) { - Log.warning(new ORMException("Trying to do an UPDATE with no values to SET. Query aborted.")); + Log.warning(new DBException("Trying to do an UPDATE with no values to SET. Query aborted.")); return 0; } - String sql = "UPDATE " + ORM.getTableName(elemClass) + " SET "; + String sql = "UPDATE " + DB.getTableName(elemClass) + " SET "; List params = new ArrayList<>(); boolean first = true; @@ -64,7 +64,7 @@ public class SQLUpdate> { sql += ";"; - return ORM.customUpdateStatement(sql, params); + return DB.customUpdateStatement(sql, params); } } diff --git a/Core/src/main/java/fr/pandacube/lib/core/db/SQLWhere.java b/Core/src/main/java/fr/pandacube/lib/core/db/SQLWhere.java new file mode 100644 index 0000000..799471b --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/db/SQLWhere.java @@ -0,0 +1,306 @@ +package fr.pandacube.lib.core.db; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; + +import org.javatuples.Pair; + +import fr.pandacube.lib.core.util.Log; + +public abstract class SQLWhere> { + + public abstract Pair> toSQL() throws DBException; + + @Override + public String toString() { + try { + return toSQL().getValue0(); + } catch (DBException e) { + Log.warning(e); + return "[SQLWhere.toString() error (see logs)]"; + } + } + + public SQLWhereAnd and(SQLWhere other) { + return new SQLWhereAnd().and(this).and(other); + } + + public SQLWhereOr or(SQLWhere other) { + return new SQLWhereOr().or(this).or(other); + } + + public static > SQLWhereAnd and() { + return new SQLWhereAnd<>(); + } + + public static > SQLWhereOr or() { + return new SQLWhereOr<>(); + } + + public static String escapeLike(String str) { + return str.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%"); + } + + + + + + + + + public static abstract class SQLWhereChain> extends SQLWhere { + + private SQLBoolOp operator; + protected List> conditions = new ArrayList<>(); + + private SQLWhereChain(SQLBoolOp op) { + if (op == null) throw new IllegalArgumentException("op can't be null"); + operator = op; + } + + protected void add(SQLWhere sqlWhere) { + if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null"); + conditions.add(sqlWhere); + } + + @Override + public Pair> toSQL() throws DBException { + if (conditions.isEmpty()) { + throw new DBException("SQLWhereChain needs at least one element inside !"); + } + + String sql = ""; + List params = new ArrayList<>(); + boolean first = true; + + for (SQLWhere w : conditions) { + if (!first) sql += " " + operator.sql + " "; + first = false; + + Pair> ret = w.toSQL(); + sql += "(" + ret.getValue0() + ")"; + params.addAll(ret.getValue1()); + } + + return new Pair<>(sql, params); + } + + protected enum SQLBoolOp { + /** Equivalent to SQL "AND" */ + AND("AND"), + /** Equivalent to SQL "OR" */ + OR("OR"); + /* package */ final String sql; + + private SQLBoolOp(String s) { + sql = s; + } + + } + + } + + + + + + + + + public static class SQLWhereAnd> extends SQLWhereChain { + + private SQLWhereAnd() { + super(SQLBoolOp.AND); + } + + @Override + public SQLWhereAnd and(SQLWhere other) { + add(other); + return this; + } + + } + + + + + + + public static class SQLWhereOr> extends SQLWhereChain { + + private SQLWhereOr() { + super(SQLBoolOp.OR); + } + + @Override + public SQLWhereOr or(SQLWhere other) { + add(other); + return this; + } + + } + + + + + + + /* package */ static class SQLWhereComp> extends SQLWhere { + + private SQLField left; + private SQLComparator comp; + private Object right; + + /** + * Compare a field with a value + * + * @param l the field at left of the comparison operator. Can't be null + * @param c the comparison operator, can't be null + * @param r the value at right of the comparison operator. Can't be null + */ + /* package */ SQLWhereComp(SQLField l, SQLComparator c, T r) { + if (l == null || r == null || c == null) + throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null"); + left = l; + comp = c; + right = r; + } + + @Override + public Pair> toSQL() throws DBException { + List params = new ArrayList<>(); + SQLElement.addValueToSQLObjectList(params, left, right); + return new Pair<>("`" + left.getName() + "` " + comp.sql + " ? ", params); + } + + /* package */ enum SQLComparator { + /** Equivalent to SQL "=" */ + EQ("="), + /** Equivalent to SQL ">" */ + GT(">"), + /** Equivalent to SQL ">=" */ + GEQ(">="), + /** Equivalent to SQL "<" */ + LT("<"), + /** Equivalent to SQL "<=" */ + LEQ("<="), + /** Equivalent to SQL "!=" */ + NEQ("!="); + + /* package */ final String sql; + + private SQLComparator(String s) { + sql = s; + } + + } + + } + + + + + + + /* package */ static class SQLWhereIn> extends SQLWhere { + + private SQLField field; + private Collection values; + + /* package */ SQLWhereIn(SQLField f, Collection v) { + if (f == null || v == null) + throw new IllegalArgumentException("All arguments for SQLWhereIn constructor can't be null"); + field = f; + values = v; + } + + @Override + public Pair> toSQL() throws DBException { + List params = new ArrayList<>(); + + if (values.isEmpty()) + return new Pair<>(" 1=0 ", params); + + for (Object v : values) + SQLElement.addValueToSQLObjectList(params, field, v); + + char[] questions = new char[values.size() == 0 ? 0 : (values.size() * 2 - 1)]; + for (int i = 0; i < questions.length; i++) + questions[i] = i % 2 == 0 ? '?' : ','; + + return new Pair<>("`" + field.getName() + "` IN (" + new String(questions) + ") ", params); + } + + } + + + + + + + + + /* package */ static class SQLWhereLike> extends SQLWhere { + + private SQLField field; + private String likeExpr; + + /** + * Compare a field with a value + * + * @param f the field at left of the LIKE keyword. Can't be null + * @param like the like expression. + */ + /* package */ SQLWhereLike(SQLField f, String like) { + if (f == null || like == null) + throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null"); + field = f; + likeExpr = like; + } + + @Override + public Pair> toSQL() { + ArrayList params = new ArrayList<>(); + params.add(likeExpr); + return new Pair<>("`" + field.getName() + "` LIKE ? ", params); + } + + } + + + + + + + + /* package */ static class SQLWhereNull> extends SQLWhere { + + private SQLField fild; + private boolean nulll; + + /** + * Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition. + * + * @param field the field to check null / not null state + * @param isNull true if we want to ckeck if "IS NULL", or false to check if + * "IS NOT NULL" + */ + /* package */ SQLWhereNull(SQLField field, boolean isNull) { + if (field == null) throw new IllegalArgumentException("field can't be null"); + if (!field.canBeNull) Log.getLogger().log(Level.WARNING, + "Useless : Trying to check IS [NOT] NULL on the field " + field.getSQLElementType().getName() + "#" + + field.getName() + " which is declared in the ORM as 'can't be null'"); + fild = field; + nulll = isNull; + } + + @Override + public Pair> toSQL() { + return new Pair<>("`" + fild.getName() + "` IS " + ((nulll) ? "NULL" : "NOT NULL"), new ArrayList<>()); + } + + } + +} diff --git a/Core/src/main/java/fr/pandacube/util/net/Array8Bit.java b/Core/src/main/java/fr/pandacube/lib/core/net/Array8Bit.java similarity index 96% rename from Core/src/main/java/fr/pandacube/util/net/Array8Bit.java rename to Core/src/main/java/fr/pandacube/lib/core/net/Array8Bit.java index 8a3c22a..10f45a9 100644 --- a/Core/src/main/java/fr/pandacube/util/net/Array8Bit.java +++ b/Core/src/main/java/fr/pandacube/lib/core/net/Array8Bit.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.net; +package fr.pandacube.lib.core.net; import java.util.Arrays; diff --git a/Core/src/main/java/fr/pandacube/util/net/ByteBuffer.java b/Core/src/main/java/fr/pandacube/lib/core/net/ByteBuffer.java similarity index 96% rename from Core/src/main/java/fr/pandacube/util/net/ByteBuffer.java rename to Core/src/main/java/fr/pandacube/lib/core/net/ByteBuffer.java index 80dac7f..0af8b1b 100644 --- a/Core/src/main/java/fr/pandacube/util/net/ByteBuffer.java +++ b/Core/src/main/java/fr/pandacube/lib/core/net/ByteBuffer.java @@ -1,13 +1,14 @@ -package fr.pandacube.util.net; +package fr.pandacube.lib.core.net; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import fr.pandacube.Pandacube; - public final class ByteBuffer implements Cloneable { + public static final Charset NETWORK_CHARSET = Charset.forName("UTF-8"); + private java.nio.ByteBuffer buff; public ByteBuffer() { @@ -223,7 +224,7 @@ public final class ByteBuffer implements Cloneable { if (s == null) { return putInt(-1); } - return putSizedByteArray(s.getBytes(Pandacube.NETWORK_CHARSET)); + return putSizedByteArray(s.getBytes(NETWORK_CHARSET)); } /** @@ -232,7 +233,7 @@ public final class ByteBuffer implements Cloneable { */ public String getString() { byte[] binaryString = getSizedByteArray(); - return (binaryString == null) ? null : new String(binaryString, Pandacube.NETWORK_CHARSET); + return (binaryString == null) ? null : new String(binaryString, NETWORK_CHARSET); } /** diff --git a/Core/src/main/java/fr/pandacube/util/net/PPacket.java b/Core/src/main/java/fr/pandacube/lib/core/net/PPacket.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/net/PPacket.java rename to Core/src/main/java/fr/pandacube/lib/core/net/PPacket.java index 635e968..fcaa093 100644 --- a/Core/src/main/java/fr/pandacube/util/net/PPacket.java +++ b/Core/src/main/java/fr/pandacube/lib/core/net/PPacket.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.net; +package fr.pandacube.lib.core.net; import java.util.Arrays; diff --git a/Core/src/main/java/fr/pandacube/util/net/PPacketAnswer.java b/Core/src/main/java/fr/pandacube/lib/core/net/PPacketAnswer.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/net/PPacketAnswer.java rename to Core/src/main/java/fr/pandacube/lib/core/net/PPacketAnswer.java index 308678d..a2cf36e 100644 --- a/Core/src/main/java/fr/pandacube/util/net/PPacketAnswer.java +++ b/Core/src/main/java/fr/pandacube/lib/core/net/PPacketAnswer.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.net; +package fr.pandacube.lib.core.net; import java.util.Arrays; diff --git a/Core/src/main/java/fr/pandacube/util/net/PPacketListener.java b/Core/src/main/java/fr/pandacube/lib/core/net/PPacketListener.java similarity index 89% rename from Core/src/main/java/fr/pandacube/util/net/PPacketListener.java rename to Core/src/main/java/fr/pandacube/lib/core/net/PPacketListener.java index bd88f4f..385845c 100644 --- a/Core/src/main/java/fr/pandacube/util/net/PPacketListener.java +++ b/Core/src/main/java/fr/pandacube/lib/core/net/PPacketListener.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.net; +package fr.pandacube.lib.core.net; @FunctionalInterface public interface PPacketListener

{ diff --git a/Core/src/main/java/fr/pandacube/util/net/PServer.java b/Core/src/main/java/fr/pandacube/lib/core/net/PServer.java similarity index 93% rename from Core/src/main/java/fr/pandacube/util/net/PServer.java rename to Core/src/main/java/fr/pandacube/lib/core/net/PServer.java index 9551612..e061ddb 100644 --- a/Core/src/main/java/fr/pandacube/util/net/PServer.java +++ b/Core/src/main/java/fr/pandacube/lib/core/net/PServer.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.net; +package fr.pandacube.lib.core.net; import java.io.Closeable; import java.io.IOException; @@ -14,8 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang.builder.ToStringBuilder; -import fr.pandacube.Pandacube; -import fr.pandacube.util.Log; +import fr.pandacube.lib.core.util.Log; public class PServer extends Thread implements Closeable { private static AtomicInteger connectionCounterId = new AtomicInteger(0); @@ -51,14 +50,14 @@ public class PServer extends Thread implements Closeable { try { socket = new ServerSocket(); - socket.setReceiveBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE); + socket.setReceiveBufferSize(PSocket.NETWORK_TCP_BUFFER_SIZE); socket.setPerformancePreferences(0, 1, 0); socket.bind(new InetSocketAddress(port)); while (true) { Socket socketClient = socket.accept(); - socketClient.setSendBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE); - socketClient.setSoTimeout(Pandacube.NETWORK_TIMEOUT); + socketClient.setSendBufferSize(PSocket.NETWORK_TCP_BUFFER_SIZE); + socketClient.setSoTimeout(PSocket.NETWORK_TIMEOUT); try { @SuppressWarnings("resource") diff --git a/Core/src/main/java/fr/pandacube/util/net/PSocket.java b/Core/src/main/java/fr/pandacube/lib/core/net/PSocket.java similarity index 94% rename from Core/src/main/java/fr/pandacube/util/net/PSocket.java rename to Core/src/main/java/fr/pandacube/lib/core/net/PSocket.java index 4ef0e7b..b4c6a8f 100644 --- a/Core/src/main/java/fr/pandacube/util/net/PSocket.java +++ b/Core/src/main/java/fr/pandacube/lib/core/net/PSocket.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.net; +package fr.pandacube.lib.core.net; import java.io.BufferedOutputStream; import java.io.Closeable; @@ -19,8 +19,7 @@ import org.apache.commons.lang.builder.ToStringBuilder; import com.google.common.base.Objects; -import fr.pandacube.Pandacube; -import fr.pandacube.util.Log; +import fr.pandacube.lib.core.util.Log; /** * A wrapper for a {@link Socket}. The connection must point to a software using {@link PServer} @@ -35,6 +34,10 @@ import fr.pandacube.util.Log; * */ public class PSocket extends Thread implements Closeable { + + public static final int NETWORK_TCP_BUFFER_SIZE = 1024 * 1024; + + public static final int NETWORK_TIMEOUT = 0; // no timeout (milli-seconds) private boolean server = false; private Socket socket; @@ -80,8 +83,8 @@ public class PSocket extends Thread implements Closeable { try { if (socket == null) { socket = new Socket(); - socket.setReceiveBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE); - socket.setSendBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE); + socket.setReceiveBufferSize(NETWORK_TCP_BUFFER_SIZE); + socket.setSendBufferSize(NETWORK_TCP_BUFFER_SIZE); socket.setSoTimeout(10000); // initial timeout before login @@ -137,7 +140,7 @@ public class PSocket extends Thread implements Closeable { password = null; } - socket.setSoTimeout(Pandacube.NETWORK_TIMEOUT); + socket.setSoTimeout(NETWORK_TIMEOUT); Log.info(getName() + " connected."); @@ -202,7 +205,7 @@ public class PSocket extends Thread implements Closeable { byte[] nBytes = new byte[nSize]; in.readFully(nBytes); - String name = new String(nBytes, Pandacube.NETWORK_CHARSET); + String name = new String(nBytes, ByteBuffer.NETWORK_CHARSET); int packetId = in.readInt(); @@ -241,7 +244,7 @@ public class PSocket extends Thread implements Closeable { if (packet.content == null) throw new IllegalArgumentException("packet.content can't be null"); - byte[] nameBytes = packet.name.getBytes(Pandacube.NETWORK_CHARSET); + byte[] nameBytes = packet.name.getBytes(ByteBuffer.NETWORK_CHARSET); if (nameBytes.length > 127) throw new IllegalArgumentException("packet.name must take fewer than 128 bytes when converted to UTF-8"); byte nameSize = (byte)nameBytes.length; diff --git a/Core/src/main/java/fr/pandacube/util/net/PSocketConnectionListener.java b/Core/src/main/java/fr/pandacube/lib/core/net/PSocketConnectionListener.java similarity index 90% rename from Core/src/main/java/fr/pandacube/util/net/PSocketConnectionListener.java rename to Core/src/main/java/fr/pandacube/lib/core/net/PSocketConnectionListener.java index db4d865..8eeeb96 100644 --- a/Core/src/main/java/fr/pandacube/util/net/PSocketConnectionListener.java +++ b/Core/src/main/java/fr/pandacube/lib/core/net/PSocketConnectionListener.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.net; +package fr.pandacube.lib.core.net; public interface PSocketConnectionListener { diff --git a/Core/src/main/java/fr/pandacube/util/network_api/client/AbstractRequest.java b/Core/src/main/java/fr/pandacube/lib/core/network_api/client/AbstractRequest.java similarity index 92% rename from Core/src/main/java/fr/pandacube/util/network_api/client/AbstractRequest.java rename to Core/src/main/java/fr/pandacube/lib/core/network_api/client/AbstractRequest.java index e4aed64..07fb39d 100644 --- a/Core/src/main/java/fr/pandacube/util/network_api/client/AbstractRequest.java +++ b/Core/src/main/java/fr/pandacube/lib/core/network_api/client/AbstractRequest.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.network_api.client; +package fr.pandacube.lib.core.network_api.client; import java.io.PrintStream; diff --git a/Core/src/main/java/fr/pandacube/util/network_api/client/NetworkAPISender.java b/Core/src/main/java/fr/pandacube/lib/core/network_api/client/NetworkAPISender.java similarity index 91% rename from Core/src/main/java/fr/pandacube/util/network_api/client/NetworkAPISender.java rename to Core/src/main/java/fr/pandacube/lib/core/network_api/client/NetworkAPISender.java index d32dc3a..79c3f52 100644 --- a/Core/src/main/java/fr/pandacube/util/network_api/client/NetworkAPISender.java +++ b/Core/src/main/java/fr/pandacube/lib/core/network_api/client/NetworkAPISender.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.network_api.client; +package fr.pandacube.lib.core.network_api.client; import java.io.IOException; import java.io.PrintStream; diff --git a/Core/src/main/java/fr/pandacube/util/network_api/client/ResponseAnalyser.java b/Core/src/main/java/fr/pandacube/lib/core/network_api/client/ResponseAnalyser.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/network_api/client/ResponseAnalyser.java rename to Core/src/main/java/fr/pandacube/lib/core/network_api/client/ResponseAnalyser.java index cb50435..d80a8f7 100644 --- a/Core/src/main/java/fr/pandacube/util/network_api/client/ResponseAnalyser.java +++ b/Core/src/main/java/fr/pandacube/lib/core/network_api/client/ResponseAnalyser.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.network_api.client; +package fr.pandacube.lib.core.network_api.client; import java.io.BufferedReader; import java.io.IOException; diff --git a/Core/src/main/java/fr/pandacube/util/network_api/server/AbstractRequestExecutor.java b/Core/src/main/java/fr/pandacube/lib/core/network_api/server/AbstractRequestExecutor.java similarity index 92% rename from Core/src/main/java/fr/pandacube/util/network_api/server/AbstractRequestExecutor.java rename to Core/src/main/java/fr/pandacube/lib/core/network_api/server/AbstractRequestExecutor.java index cd8d350..f5c9a5c 100644 --- a/Core/src/main/java/fr/pandacube/util/network_api/server/AbstractRequestExecutor.java +++ b/Core/src/main/java/fr/pandacube/lib/core/network_api/server/AbstractRequestExecutor.java @@ -1,11 +1,11 @@ -package fr.pandacube.util.network_api.server; +package fr.pandacube.lib.core.network_api.server; import java.io.IOException; import java.io.PrintStream; import java.net.InetAddress; import java.net.Socket; -import fr.pandacube.util.Log; +import fr.pandacube.lib.core.util.Log; @Deprecated public abstract class AbstractRequestExecutor { diff --git a/Core/src/main/java/fr/pandacube/util/network_api/server/NetworkAPIListener.java b/Core/src/main/java/fr/pandacube/lib/core/network_api/server/NetworkAPIListener.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/network_api/server/NetworkAPIListener.java rename to Core/src/main/java/fr/pandacube/lib/core/network_api/server/NetworkAPIListener.java index ae9e53c..dc4f821 100644 --- a/Core/src/main/java/fr/pandacube/util/network_api/server/NetworkAPIListener.java +++ b/Core/src/main/java/fr/pandacube/lib/core/network_api/server/NetworkAPIListener.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.network_api.server; +package fr.pandacube.lib.core.network_api.server; import java.io.IOException; import java.net.ServerSocket; diff --git a/Core/src/main/java/fr/pandacube/util/network_api/server/PacketExecutor.java b/Core/src/main/java/fr/pandacube/lib/core/network_api/server/PacketExecutor.java similarity index 90% rename from Core/src/main/java/fr/pandacube/util/network_api/server/PacketExecutor.java rename to Core/src/main/java/fr/pandacube/lib/core/network_api/server/PacketExecutor.java index 1d1fb81..25eb5c7 100644 --- a/Core/src/main/java/fr/pandacube/util/network_api/server/PacketExecutor.java +++ b/Core/src/main/java/fr/pandacube/lib/core/network_api/server/PacketExecutor.java @@ -1,11 +1,11 @@ -package fr.pandacube.util.network_api.server; +package fr.pandacube.lib.core.network_api.server; import java.io.IOException; import java.io.PrintStream; import java.net.Socket; -import fr.pandacube.util.Log; -import fr.pandacube.util.network_api.server.RequestAnalyser.BadRequestException; +import fr.pandacube.lib.core.network_api.server.RequestAnalyser.BadRequestException; +import fr.pandacube.lib.core.util.Log; /** * Prends en charge un socket client et le transmet au gestionnaire de paquet diff --git a/Core/src/main/java/fr/pandacube/util/network_api/server/RequestAnalyser.java b/Core/src/main/java/fr/pandacube/lib/core/network_api/server/RequestAnalyser.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/network_api/server/RequestAnalyser.java rename to Core/src/main/java/fr/pandacube/lib/core/network_api/server/RequestAnalyser.java index 591bca5..91db69a 100644 --- a/Core/src/main/java/fr/pandacube/util/network_api/server/RequestAnalyser.java +++ b/Core/src/main/java/fr/pandacube/lib/core/network_api/server/RequestAnalyser.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.network_api.server; +package fr.pandacube.lib.core.network_api.server; import java.io.BufferedReader; import java.io.IOException; diff --git a/Core/src/main/java/fr/pandacube/util/network_api/server/Response.java b/Core/src/main/java/fr/pandacube/lib/core/network_api/server/Response.java similarity index 91% rename from Core/src/main/java/fr/pandacube/util/network_api/server/Response.java rename to Core/src/main/java/fr/pandacube/lib/core/network_api/server/Response.java index 0304980..755679d 100644 --- a/Core/src/main/java/fr/pandacube/util/network_api/server/Response.java +++ b/Core/src/main/java/fr/pandacube/lib/core/network_api/server/Response.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.network_api.server; +package fr.pandacube.lib.core.network_api.server; import java.io.PrintStream; diff --git a/Core/src/main/java/fr/pandacube/lib/core/permissions/PermEntity.java b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermEntity.java new file mode 100644 index 0000000..2ebd099 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermEntity.java @@ -0,0 +1,167 @@ +package fr.pandacube.lib.core.permissions; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import fr.pandacube.lib.core.chat.ChatUtil.DisplayTreeNode; +import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedEntity; +import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType; + +public abstract class PermEntity { + protected final String name; + protected final EntityType type; + protected PermEntity(String n, EntityType t) { + name = n; type = t; + } + + protected abstract CachedEntity getBackendEntity(); + public abstract List getInheritances(); + public abstract List getInheritancesString(); + public abstract String getName(); + + public String getInternalName() { + return name; + } + + + /** + * Tells if the current entity inherits directly or indirectly from the specified group + * @param group the group to search for + * @param recursive true to search in the inheritance tree, or false to search only in the inheritance list of the current entity. + * @return + */ + public boolean inheritsFromGroup(String group, boolean recursive) { + if (group == null) + return false; + return getInheritances().stream().anyMatch(g -> g.name.equals(group) || (recursive && g.inheritsFromGroup(group, recursive))); + } + + public String getPrefix() { + return Permissions.resolver.getEffectivePrefix(name, type); + } + + + public String getSelfPrefix() { + return getBackendEntity().getSelfPrefix(); + } + + + public DisplayTreeNode debugPrefix() { + return Permissions.resolver.debugPrefix(name, type); + } + + + public void setSelfPrefix(String prefix) { + Permissions.backendWriter.setSelfPrefix(name, type, prefix); + } + + + public String getSuffix() { + return Permissions.resolver.getEffectiveSuffix(name, type); + } + + public String getSelfSuffix() { + return getBackendEntity().getSelfSuffix(); + } + + + public DisplayTreeNode debugSuffix() { + return Permissions.resolver.debugSuffix(name, type); + } + + + public void setSelfSuffix(String suffix) { + Permissions.backendWriter.setSelfSuffix(name, type, suffix); + } + + + public Boolean hasPermission(String permission) { + return Permissions.resolver.getEffectivePermission(name, type, permission, null, null); + } + + public Boolean hasPermission(String permission, String server) { + return Permissions.resolver.getEffectivePermission(name, type, permission, server, null); + } + + public Boolean hasPermission(String permission, String server, String world) { + return Permissions.resolver.getEffectivePermission(name, type, permission, server, world); + } + + + public DisplayTreeNode debugPermission(String permission) { + return Permissions.resolver.debugPermission(name, type, permission, null, null); + } + + public DisplayTreeNode debugPermission(String permission, String server) { + return Permissions.resolver.debugPermission(name, type, permission, server, null); + } + + public DisplayTreeNode debugPermission(String permission, String server, String world) { + return Permissions.resolver.debugPermission(name, type, permission, server, world); + } + + + public void addSelfPermission(String permission) { + Permissions.backendWriter.addSelfPermission(name, type, permission, null, null); + } + + public void addSelfPermission(String permission, String server) { + Permissions.backendWriter.addSelfPermission(name, type, permission, server, null); + } + + public void addSelfPermission(String permission, String server, String world) { + Permissions.backendWriter.addSelfPermission(name, type, permission, server, world); + } + + + public void removeSelfPermission(String permission) { + Permissions.backendWriter.removeSelfPermission(name, type, permission, null, null); + } + + public void removeSelfPermission(String permission, String server) { + Permissions.backendWriter.removeSelfPermission(name, type, permission, server, null); + } + + public void removeSelfPermission(String permission, String server, String world) { + Permissions.backendWriter.removeSelfPermission(name, type, permission, server, world); + } + + public int getSelfPermissionsCount() { + return getSelfPermissionsServerWorldKeys().stream() + .mapToInt(key -> getSelfPermissions(key.server, key.world).size()) + .sum(); + } + + public Set getSelfPermissionsServerWorldKeys() { + return getBackendEntity().getSelfPermissionsServerWorldKeys(); + } + + public List getSelfPermissions() { + return getBackendEntity().getSelfPermissions(null, null); + } + + public List getSelfPermissions(String server) { + return getBackendEntity().getSelfPermissions(server, null); + } + + public List getSelfPermissions(String server, String world) { + return getBackendEntity().getSelfPermissions(server, world); + } + + + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof PermEntity)) + return false; + PermEntity o = (PermEntity) obj; + return Objects.equals(name, o.name) && type == o.type; + } + + @Override + public int hashCode() { + return Objects.hash(name, type); + } + +} \ No newline at end of file diff --git a/Core/src/main/java/fr/pandacube/lib/core/permissions/PermGroup.java b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermGroup.java new file mode 100644 index 0000000..c10b87a --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermGroup.java @@ -0,0 +1,64 @@ +package fr.pandacube.lib.core.permissions; + +import java.util.List; +import java.util.stream.Collectors; + +import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedGroup; +import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType; + +public class PermGroup extends PermEntity { + /* package */ PermGroup(String name) { + super(name, EntityType.Group); + } + @Override + protected CachedGroup getBackendEntity() { + return Permissions.backendReader.getCachedGroup(name); + } + + @Override + public String getName() { + return getInternalName(); + } + + @Override + public List getInheritances() { + return fromCachedGroups(getBackendEntity().inheritances); + } + + @Override + public List getInheritancesString() { + return getBackendEntity().inheritances.stream() + .map(cg -> cg.name) + .collect(Collectors.toList()); + } + + public boolean isDefault() { + return getBackendEntity().deflt; + } + + public void setDefault(boolean deflt) { + Permissions.backendWriter.setGroupDefault(name, deflt); + } + + public void addInheritance(String group) { + Permissions.backendWriter.addInheritance(name, type, group); + } + + public void addInheritance(PermGroup group) { + addInheritance(group.name); + } + + public void removeInheritance(String group) { + Permissions.backendWriter.removeInheritance(name, type, group); + } + + public void removeInheritance(PermGroup group) { + removeInheritance(group.name); + } + + /* package */ static List fromCachedGroups(List in) { + return in.stream() + .map(cg -> Permissions.getGroup(cg.name)) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/Core/src/main/java/fr/pandacube/lib/core/permissions/PermPlayer.java b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermPlayer.java new file mode 100644 index 0000000..0a9d4e7 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermPlayer.java @@ -0,0 +1,104 @@ +package fr.pandacube.lib.core.permissions; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedPlayer; +import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType; +import fr.pandacube.lib.core.players.PlayerFinder; + +public class PermPlayer extends PermEntity { + private final UUID playerId; + /* package */ PermPlayer(UUID id) { + super(id.toString(), EntityType.User); + playerId = id; + } + @Override + protected CachedPlayer getBackendEntity() { + return Permissions.backendReader.getCachedPlayer(playerId); + } + + @Override + public List getInheritances() { + return PermGroup.fromCachedGroups(getBackendEntity().groups); + } + + @Override + public List getInheritancesString() { + return getBackendEntity().groups.stream() + .map(cg -> cg.name) + .collect(Collectors.toList()); + } + + public UUID getPlayerId() { + return playerId; + } + + private String cachedPlayerName; + @Override + public synchronized String getName() { + if (cachedPlayerName == null) + cachePlayerName(); + return cachedPlayerName; + } + private void cachePlayerName() { + cachedPlayerName = PlayerFinder.getLastKnownName(playerId); + if (cachedPlayerName == null) + cachedPlayerName = playerId.toString(); + } + + /** + * Alias for {@link #getInheritances()}. + * @return + */ + public List getGroups() { + return getInheritances(); + } + + /** + * Alias for {@link #getInheritances()}. + * @return + */ + public List getGroupsString() { + return getInheritancesString(); + } + + /** + * Tells if the player is directly part of a group. + * This is equivalent to {@link #inheritsFromGroup(String, boolean) inheritsFromGroup(group, false)} + * @param group the group to search for + * @return + */ + public boolean isInGroup(String group) { + return inheritsFromGroup(group, false); + } + + public boolean isUsingDefaultGroups() { + return getBackendEntity().usingDefaultGroups; + } + + public void setGroup(String group) { + Permissions.backendWriter.setInheritance(name, type, group); + } + + public void setGroup(PermGroup group) { + setGroup(group.name); + } + + public void addGroup(String group) { + Permissions.backendWriter.addInheritance(name, type, group); + } + + public void addGroup(PermGroup group) { + addGroup(group.name); + } + + public void removeGroup(String group) { + Permissions.backendWriter.removeInheritance(name, type, group); + } + + public void removeGroup(PermGroup group) { + removeGroup(group.name); + } +} \ No newline at end of file diff --git a/Core/src/main/java/fr/pandacube/lib/core/permissions/Permissions.java b/Core/src/main/java/fr/pandacube/lib/core/permissions/Permissions.java new file mode 100644 index 0000000..503908d --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/permissions/Permissions.java @@ -0,0 +1,96 @@ +package fr.pandacube.lib.core.permissions; + +import java.util.List; +import java.util.UUID; + +import fr.pandacube.lib.core.db.DB; +import fr.pandacube.lib.core.db.DBException; +import fr.pandacube.lib.core.util.Log; + +public class Permissions { + + /* package */ static PermissionsCachedBackendReader backendReader; + /* package */ static PermissionsResolver resolver; + /* package */ static PermissionsBackendWriter backendWriter; + + /** + * Initialize the permission system. + * The connection to the database needs to be initialized first, using {@link DB#init(DBConnection, String)}. + * @throws DBException + */ + public static void init() throws DBException { + if (backendReader != null) + return; + try { + DB.initTable(SQLPermissions.class); + backendReader = new PermissionsCachedBackendReader(); + resolver = new PermissionsResolver(backendReader); + backendWriter = new PermissionsBackendWriter(); + } catch (Exception e) { + backendReader = null; + resolver = null; + backendWriter = null; + throw e; + } + } + + private static void checkInitialized() { + if (backendReader == null) { + throw new IllegalStateException("Permissions system not initialized. Check the server logs to check if there is an error during the startup, and check if the init() method is called properly."); + } + } + + public static void clearPlayerCache(UUID playerId) { + checkInitialized(); + backendReader.clearPlayerCache(playerId); + resolver.clearPlayerFromCache(playerId); + } + + public static void clearCache(Runnable then) { + checkInitialized(); + backendReader.clearAndResetCacheAsync(() -> { + resolver.clearCache(); + if (then != null) + then.run(); + }); + } + + + public static PermPlayer getPlayer(UUID playerId) { + checkInitialized(); + return new PermPlayer(playerId); + } + + public static void precachePlayerAsync(UUID playerId) { + checkInitialized(); + Thread t = new Thread(() -> { + try { + backendReader.getCachedPlayer(playerId); + } catch (RuntimeException e) { + Log.warning("Can’t init player cache asynchronously: " + e.getMessage()); + } + }, "Async permissions player cache loader"); + t.setDaemon(true); + t.start(); + } + + public static PermGroup getGroup(String name) { + checkInitialized(); + return new PermGroup(name); + } + + public static List getGroups() { + checkInitialized(); + return PermGroup.fromCachedGroups(backendReader.getGroups()); + } + + public static List getDefaultGroups() { + checkInitialized(); + return PermGroup.fromCachedGroups(backendReader.getDefaultGroups()); + } + + public static List getFullPermissionsList() { + return backendReader.getFullPermissionsList(); + } + +} \ No newline at end of file diff --git a/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionsBackendWriter.java b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionsBackendWriter.java new file mode 100644 index 0000000..6029063 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionsBackendWriter.java @@ -0,0 +1,293 @@ +package fr.pandacube.lib.core.permissions; + +import com.google.common.base.Preconditions; + +import fr.pandacube.lib.core.db.DB; +import fr.pandacube.lib.core.db.DBException; +import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType; + +/* package */ class PermissionsBackendWriter { + + + /* package */ void addSelfPermission(String name, EntityType type, String permission, String server, String world) { + Preconditions.checkNotNull(name, "name cannot be null"); + Preconditions.checkNotNull(type, "type cannot be null"); + Preconditions.checkNotNull(permission, "permission cannot be null"); + Preconditions.checkArgument(world == null || server != null, "world not null but server is null"); + name = name.toLowerCase(); + permission = permission.toLowerCase(); + if (server != null) server = server.toLowerCase(); + if (world != null) world = world.toLowerCase(); + + if (hasEntry(name, type, "permissions", permission, server, world)) + throw new IllegalStateException("Permission already set"); + addEntry(name, type, "permissions", permission, server, world); + } + + /* package */ void removeSelfPermission(String name, EntityType type, String permission, String server, String world) { + Preconditions.checkNotNull(name, "name cannot be null"); + Preconditions.checkNotNull(type, "type cannot be null"); + Preconditions.checkNotNull(permission, "permission cannot be null"); + Preconditions.checkArgument(world == null || server != null, "world not null but server is null"); + name = name.toLowerCase(); + permission = permission.toLowerCase(); + if (server != null) server = server.toLowerCase(); + if (world != null) world = world.toLowerCase(); + + if (!deleteEntry(name, type, "permissions", permission, server, world)) + throw new IllegalStateException("Permission was not set"); + } + + + + + + + + + + /* package */ void setGroupDefault(String name, boolean deflt) { + Preconditions.checkNotNull(name, "name cannot be null"); + name = name.toLowerCase(); + try { + SQLPermissions entry = DB.getFirst(SQLPermissions.class, + SQLPermissions.name.like(name) + .and(SQLPermissions.type.eq(EntityType.Group.getCode())) + .and(SQLPermissions.key.like("default")) + ); + if (entry == null && !deflt) { + return; + } + else if (entry != null && deflt) { + // update + if ("true".equals(entry.get(SQLPermissions.value))) + return; + entry.set(SQLPermissions.value, "true"); + entry.save(); + return; + } + else if (entry != null && !deflt) { + // delete + entry.delete(); + } + else { + // insert + addEntry(name, EntityType.Group, "default", "true", null, null); + } + } catch (DBException e) { + throw new RuntimeException(e); + } + } + + + + + + + /* package */ void setSelfPrefix(String name, EntityType type, String prefix) { + Preconditions.checkNotNull(name, "name cannot be null"); + Preconditions.checkNotNull(type, "type cannot be null"); + name = name.toLowerCase(); + + try { + SQLPermissions entry = DB.getFirst(SQLPermissions.class, + SQLPermissions.name.like(name) + .and(SQLPermissions.type.eq(type.getCode())) + .and(SQLPermissions.key.like("prefix")) + ); + if (entry == null && prefix == null) { + return; + } + else if (entry != null && prefix != null) { + // update + entry.set(SQLPermissions.value, prefix); + entry.save(); + return; + } + else if (entry != null && prefix == null) { + // delete + entry.delete(); + } + else { + // insert + addEntry(name, type, "prefix", prefix, null, null); + } + } catch (DBException e) { + throw new RuntimeException(e); + } + } + + /* package */ void setSelfSuffix(String name, EntityType type, String suffix) { + Preconditions.checkNotNull(name, "name cannot be null"); + Preconditions.checkNotNull(type, "type cannot be null"); + name = name.toLowerCase(); + + try { + SQLPermissions entry = DB.getFirst(SQLPermissions.class, + SQLPermissions.name.like(name) + .and(SQLPermissions.type.eq(type.getCode())) + .and(SQLPermissions.key.like("suffix")) + ); + if (entry == null && suffix == null) { + return; + } + else if (entry != null && suffix != null) { + // update + entry.set(SQLPermissions.value, suffix); + entry.save(); + return; + } + else if (entry != null && suffix == null) { + // delete + entry.delete(); + } + else { + // insert + addEntry(name, type, "suffix", suffix, null, null); + } + } catch (DBException e) { + throw new RuntimeException(e); + } + } + + + + + + /* package */ void addInheritance(String name, EntityType type, String inheritance) { + Preconditions.checkNotNull(name, "name cannot be null"); + Preconditions.checkNotNull(type, "type cannot be null"); + Preconditions.checkNotNull(inheritance, "inheritance cannot be null"); + name = name.toLowerCase(); + inheritance = inheritance.toLowerCase(); + String key = type == EntityType.Group ? "inheritances" : "groups"; + + try { + SQLPermissions entry = DB.getFirst(SQLPermissions.class, + SQLPermissions.name.like(name) + .and(SQLPermissions.type.eq(type.getCode())) + .and(SQLPermissions.key.like(key)) + .and(SQLPermissions.value.like(inheritance)) + ); + if (entry != null) + throw new IllegalStateException("Inheritance already set"); + addEntry(name, type, key, inheritance, null, null); + } catch (DBException e) { + new RuntimeException(e); + } + + } + + /* package */ void removeInheritance(String name, EntityType type, String inheritance) { + Preconditions.checkNotNull(name, "name cannot be null"); + Preconditions.checkNotNull(type, "type cannot be null"); + Preconditions.checkNotNull(inheritance, "inheritance cannot be null"); + name = name.toLowerCase(); + inheritance = inheritance.toLowerCase(); + String key = type == EntityType.Group ? "inheritances" : "groups"; + + try { + int deleted = DB.delete(SQLPermissions.class, + SQLPermissions.name.like(name) + .and(SQLPermissions.type.eq(type.getCode())) + .and(SQLPermissions.key.like(key)) + .and(SQLPermissions.value.like(inheritance)) + ); + if (deleted == 0) + throw new IllegalStateException("Inheritance was not set"); + } catch (DBException e) { + throw new RuntimeException(e); + } + } + + /* package */ void setInheritance(String name, EntityType type, String inheritance) { + Preconditions.checkNotNull(name, "name cannot be null"); + Preconditions.checkNotNull(type, "type cannot be null"); + Preconditions.checkNotNull(inheritance, "inheritance cannot be null"); + name = name.toLowerCase(); + inheritance = inheritance.toLowerCase(); + String key = type == EntityType.Group ? "inheritances" : "groups"; + + try { + DB.delete(SQLPermissions.class, + SQLPermissions.name.like(name) + .and(SQLPermissions.type.eq(type.getCode())) + .and(SQLPermissions.key.like(key)) + ); + addEntry(name, type, key, inheritance, null, null); + } catch (DBException e) { + throw new RuntimeException(e); + } + } + + + + + + + + + + + + + + + private boolean deleteEntry(String name, EntityType type, + String key, String value, + String server, String world) { + try { + return DB.delete(SQLPermissions.class, + SQLPermissions.name.like(name) + .and(SQLPermissions.type.eq(type.getCode())) + .and(SQLPermissions.key.like(key)) + .and(SQLPermissions.value.like(value)) + .and(server == null ? SQLPermissions.server.isNull() : SQLPermissions.server.like(server)) + .and(world == null ? SQLPermissions.world.isNull() : SQLPermissions.world.like(world)) + ) >= 1; + } catch (DBException e) { + throw new RuntimeException(e); + } + } + + private boolean hasEntry(String name, EntityType type, + String key, String value, + String server, String world) { + return getEntry(name, type, key, value, server, world) != null; + } + + private SQLPermissions getEntry(String name, EntityType type, + String key, String value, + String server, String world) { + try { + return DB.getFirst(SQLPermissions.class, + SQLPermissions.name.like(name) + .and(SQLPermissions.type.eq(type.getCode())) + .and(SQLPermissions.key.like(key)) + .and(SQLPermissions.value.like(value)) + .and(server == null ? SQLPermissions.server.isNull() : SQLPermissions.server.like(server)) + .and(world == null ? SQLPermissions.world.isNull() : SQLPermissions.world.like(world)) + ); + } catch (DBException e) { + throw new RuntimeException(e); + } + } + + private void addEntry(String name, EntityType type, + String key, String value, + String server, String world) { + SQLPermissions entry = new SQLPermissions() + .set(SQLPermissions.name, name) + .set(SQLPermissions.type, type.getCode()) + .set(SQLPermissions.key, key) + .set(SQLPermissions.value, value) + .set(SQLPermissions.server, server) + .set(SQLPermissions.world, world); + try { + entry.save(); + } catch (DBException e) { + throw new RuntimeException(e); + } + } + +} \ No newline at end of file diff --git a/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionsCachedBackendReader.java b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionsCachedBackendReader.java new file mode 100644 index 0000000..2dc70d8 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionsCachedBackendReader.java @@ -0,0 +1,307 @@ +package fr.pandacube.lib.core.permissions; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import fr.pandacube.lib.core.db.DB; +import fr.pandacube.lib.core.db.DBException; +import fr.pandacube.lib.core.db.SQLElementList; +import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType; +import fr.pandacube.lib.core.util.Log; + +/* package */ class PermissionsCachedBackendReader +{ + /* package */ PermissionsCachedBackendReader() throws DBException { + clearAndResetCache(); + } + + + + + private Cache usersCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + private Set fullPermissionsList = new TreeSet(); + + /* package */ synchronized List getFullPermissionsList() { + return new ArrayList<>(fullPermissionsList); + } + + /* package */ synchronized void clearPlayerCache(UUID playerId) { + usersCache.invalidate(playerId); + } + + /* package */ synchronized CachedPlayer getCachedPlayer(UUID playerId) { + try { + return usersCache.get(playerId, () -> { + try { + return initPlayer(playerId); + } catch (DBException e) { + throw new RuntimeException(e); + } + }); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + private CachedPlayer initPlayer(UUID playerId) throws DBException { + + SQLElementList playerData = DB.getAll(SQLPermissions.class, + SQLPermissions.type.eq(EntityType.User.getCode()) + .and(SQLPermissions.name.like(playerId.toString())) + ); + + Map> playerRawData = playerData.stream() + .collect( + Collectors.groupingBy(e -> e.get(SQLPermissions.key), + LinkedHashMap::new, + Collectors.toList()) + ); + + String playerSelfPrefix = null; + if (playerRawData.containsKey("prefix")) { + playerSelfPrefix = playerRawData.get("prefix").stream() + .map(e -> e.get(SQLPermissions.value)) + .collect(Collectors.joining()); + } + + String playerSelfSuffix = null; + if (playerRawData.containsKey("suffix")) { + playerSelfSuffix = playerRawData.get("suffix").stream() + .map(e -> e.get(SQLPermissions.value)) + .collect(Collectors.joining()); + } + + Map> playerSelfPerms = new LinkedHashMap<>(); + if (playerRawData.containsKey("permissions")) { + playerSelfPerms = playerRawData.get("permissions").stream() + .peek(e -> { + String value = e.get(SQLPermissions.value); + fullPermissionsList.add(value.substring(value.startsWith("-") ? 1 : 0)); + }) + .collect(Collectors.groupingBy(e -> new ServerWorldKey(e.get(SQLPermissions.server), e.get(SQLPermissions.world)), + LinkedHashMap::new, + Collectors.mapping(e -> e.get(SQLPermissions.value), + Collectors.toList() + ) + ) + ); + } + + CachedPlayer player = new CachedPlayer(playerId, playerSelfPrefix, playerSelfSuffix, playerSelfPerms); + + if (playerRawData.containsKey("groups")) { + playerRawData.get("groups").stream() + .map(e -> e.get(SQLPermissions.value)) + .forEach(g -> { + player.groups.add(getCachedGroup(g)); + }); + } + + if (player.groups.isEmpty()) { + player.usingDefaultGroups = true; + getDefaultGroups().forEach(player.groups::add); + } + + return player; + } + + + + + private Map groupsCache = new LinkedHashMap<>(); + private boolean cacheIsUpdating = false; + + + /* package */ synchronized CachedGroup getCachedGroup(String group) { + return groupsCache.getOrDefault(group, new CachedGroup(group, null, null, false, new LinkedHashMap<>())); + } + + /* package */ synchronized List getDefaultGroups() { + return groupsCache.values().stream() + .filter(g -> g.deflt) + .collect(Collectors.toList()); + } + + public List getGroups() { + return new ArrayList<>(groupsCache.values()); + } + + /* package */ void clearAndResetCacheAsync(Runnable then) { + synchronized (this) { + if (cacheIsUpdating) + return; + } + Thread t = new Thread(() -> { + try { + clearAndResetCache(); + } catch (Throwable e) { + Log.severe(e); + } + if (then != null) + then.run(); + }, "Permissions Backend Group Cache Updater"); + t.setDaemon(true); + t.start(); + } + + private void clearAndResetCache() throws DBException { + synchronized (this) { + if (cacheIsUpdating) + return; + cacheIsUpdating = true; + } + + try { + Map newData = new LinkedHashMap<>(); + Set newFullPermissionsList = new TreeSet<>(); + + SQLElementList groupData = DB.getAll(SQLPermissions.class, SQLPermissions.type.eq(EntityType.Group.getCode())); + + Map>> groupsRawData = groupData.stream() + .collect( + Collectors.groupingBy(e -> e.get(SQLPermissions.name), + LinkedHashMap::new, + Collectors.groupingBy(e -> e.get(SQLPermissions.key), + LinkedHashMap::new, + Collectors.toList()) + ) + ); + + for (String groupName : groupsRawData.keySet()) { + initGroup(groupName, groupsRawData, newData, newFullPermissionsList); + } + + synchronized (this) { + groupsCache.clear(); + groupsCache.putAll(newData); + cacheIsUpdating = false; + usersCache.invalidateAll(); + fullPermissionsList = newFullPermissionsList; + } + } finally { + synchronized (this) { + cacheIsUpdating = false; + } + } + + + } + + private void initGroup(String groupName, Map>> groupsRawData, Map newData, Set newFullPermissionsList) { + if (newData.containsKey(groupName)) + return; + + Map> groupRawData = groupsRawData.getOrDefault(groupName, new LinkedHashMap<>()); + + boolean groupDefault = groupRawData.containsKey("default") + ? "true".equals(groupRawData.get("default").get(0).get(SQLPermissions.value)) + : false; + + String groupSelfPrefix = null; + if (groupRawData.containsKey("prefix")) { + groupSelfPrefix = groupRawData.get("prefix").stream() + .map(e -> e.get(SQLPermissions.value)) + .collect(Collectors.joining()); + } + + String groupSelfSuffix = null; + if (groupRawData.containsKey("suffix")) { + groupSelfSuffix = groupRawData.get("suffix").stream() + .map(e -> e.get(SQLPermissions.value)) + .collect(Collectors.joining()); + } + + Map> groupSelfPerms = new LinkedHashMap<>(); + if (groupRawData.containsKey("permissions")) { + groupSelfPerms = groupRawData.get("permissions").stream() + .peek(e -> { + String value = e.get(SQLPermissions.value); + newFullPermissionsList.add(value.substring(value.startsWith("-") ? 1 : 0)); + }) + .collect(Collectors.groupingBy(e -> new ServerWorldKey(e.get(SQLPermissions.server), e.get(SQLPermissions.world)), + LinkedHashMap::new, + Collectors.mapping(e -> e.get(SQLPermissions.value), + Collectors.toList() + ) + ) + ); + } + + CachedGroup group = new CachedGroup(groupName, groupSelfPrefix, groupSelfSuffix, groupDefault, groupSelfPerms); + + newData.put(groupName, group); + + + if (groupRawData.containsKey("inheritances")) { + groupRawData.get("inheritances").stream() + .map(e -> e.get(SQLPermissions.value)) + .forEach(g -> { + initGroup(g, groupsRawData, newData, newFullPermissionsList); + group.inheritances.add(newData.get(g)); + }); + } + } + + + /* package */ static abstract class CachedEntity { + public final String name; + private final String selfPrefix, selfSuffix; + private final Map> selfPermissions; + + private CachedEntity(String n, String p, String s, + Map> perms) { + name = n; selfPrefix = p; selfSuffix = s; selfPermissions = perms; + } + + /* package */ List getSelfPermissions(String server, String world) { + return selfPermissions.getOrDefault(new ServerWorldKey(server, world), new ArrayList<>()); + } + + /* package */ Set getSelfPermissionsServerWorldKeys() { + return new TreeSet<>(selfPermissions.keySet()); + } + + /* package */ String getSelfPrefix() { + return selfPrefix; + } + + /* package */ String getSelfSuffix() { + return selfSuffix; + } + } + + /* package */ static class CachedPlayer extends CachedEntity { + public final UUID playerId; + public final List groups = new ArrayList<>(); + public boolean usingDefaultGroups = false; + private CachedPlayer(UUID pl, String p, String s, + Map> perms) { + super(pl.toString(), p, s, perms); + playerId = pl; + } + } + + /* package */ static class CachedGroup extends CachedEntity { + public final boolean deflt; + public final List inheritances = new ArrayList<>(); + private CachedGroup(String n, String p, String s, + boolean dflt, Map> perms) { + super(n, p, s, perms); + deflt = dflt; + } + } + +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionsResolver.java b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionsResolver.java new file mode 100644 index 0000000..939822e --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionsResolver.java @@ -0,0 +1,545 @@ +package fr.pandacube.lib.core.permissions; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import com.google.common.base.Preconditions; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import fr.pandacube.lib.core.chat.Chat; +import fr.pandacube.lib.core.chat.ChatUtil; +import fr.pandacube.lib.core.chat.ChatUtil.DisplayTreeNode; +import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedEntity; +import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedGroup; +import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedPlayer; +import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType; +import fr.pandacube.lib.core.players.PlayerFinder; +import fr.pandacube.lib.core.util.Log; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; + +public class PermissionsResolver { + + private PermissionsCachedBackendReader backendReader; + + /* package */ PermissionsResolver(PermissionsCachedBackendReader b) { + backendReader = b; + } + + /* package */ void clearPlayerFromCache(UUID player) { + String playerId = player.toString(); + synchronized (effectivePermissionsCache) { + effectivePermissionsCache.asMap().keySet().removeIf(k -> k.type == EntityType.User && playerId.equals(k.name)); + } + synchronized (effectiveDataCache) { + effectiveDataCache.asMap().keySet().removeIf(k -> k.type == EntityType.User && playerId.equals(k.name)); + } + } + + /* package */ void clearCache() { + effectivePermissionsCache.invalidateAll(); + effectiveDataCache.invalidateAll(); + } + + + + + + + + /* package */ String getEffectivePrefix(String name, EntityType type) { + return getEffectiveData(name, type, DataType.PREFIX); + } + /* package */ String getEffectiveSuffix(String name, EntityType type) { + return getEffectiveData(name, type, DataType.SUFFIX); + } + + /* package */ DisplayTreeNode debugPrefix(String name, EntityType type) { + return debugData(name, type, DataType.PREFIX); + } + /* package */ DisplayTreeNode debugSuffix(String name, EntityType type) { + return debugData(name, type, DataType.SUFFIX); + } + + private Cache effectiveDataCache = CacheBuilder.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .build(); + + private String getEffectiveData(String name, EntityType type, DataType dataType) { + Preconditions.checkNotNull(name, "name can’t be null"); + Preconditions.checkNotNull(type, "type can’t be null"); + + try { + return effectiveDataCache.get(new DataCacheKey(name, type, dataType), () -> { + return resolveData(name, type, dataType); + }); + } catch (ExecutionException e) { + Log.severe(e); + return null; + } + } + + private DisplayTreeNode debugData(String name, EntityType type, DataType dataType) { + CachedEntity entity = (type == EntityType.User) + ? backendReader.getCachedPlayer(UUID.fromString(name)) + : backendReader.getCachedGroup(name); + return resolveData(entity, dataType).toDisplayTreeNode(); + } + + private String resolveData(String name, EntityType type, DataType dataType) { + CachedEntity entity = (type == EntityType.User) + ? backendReader.getCachedPlayer(UUID.fromString(name)) + : backendReader.getCachedGroup(name); + DataResolutionNode resolutionResult = resolveData(entity, dataType); + + if (resolutionResult.conflict) { + Log.warning("For data " + dataType + ":"); + BaseComponent[] cmps = ChatUtil.treeView(resolutionResult.toDisplayTreeNode(), true); + for (BaseComponent cmp : cmps) + Log.warning(cmp.toLegacyText()); + } + + return resolutionResult.result != null ? resolutionResult.result : ""; + } + + private DataResolutionNode resolveData(CachedEntity entity, DataType dataType) { + // self data + DataResolutionNode resolutionNode = new DataResolutionNode(entity, dataType.getter.apply(entity), null); + if (resolutionNode.result != null) { + return resolutionNode; + } + + // check inheritances data + List inheritances = resolutionNode.entity instanceof CachedPlayer + ? ((CachedPlayer)resolutionNode.entity).groups + : ((CachedGroup)resolutionNode.entity).inheritances; + + List inheritedResults = new ArrayList<>(inheritances.size()); + + for (CachedGroup inherited : inheritances) { + inheritedResults.add(resolveData(inherited, dataType)); + } + + resolutionNode.inheritances.addAll(inheritedResults); + + if (inheritedResults.stream().anyMatch(g -> g.conflict)) + resolutionNode.conflict = true; + + Set inheritedPermissions = inheritedResults.stream() + .map(g -> g.result) + .filter(r -> r != null) + .collect(Collectors.toSet()); + + if (inheritedPermissions.size() == 1) + resolutionNode.result = inheritedPermissions.iterator().next(); + else if (inheritedPermissions.size() > 1) { + resolutionNode.conflictMessage = (resolutionNode.conflictMessage == null ? "" : (resolutionNode.conflictMessage + " ; ")) + + "Unsolvable conflict between inherited groups"; + resolutionNode.conflict = true; + } + + return resolutionNode; + } + + + private static class DataResolutionNode { + final CachedEntity entity; + String result; + String conflictMessage; + boolean conflict; + final List inheritances = new ArrayList<>(); + public DataResolutionNode(CachedEntity e, String r, String c) { + entity = e; result = r; conflictMessage = c; + conflict = c != null; + } + + public DisplayTreeNode toDisplayTreeNode() { + Chat c = Chat.text(entity.name); + if (result == null) + c.then(Chat.text(" (non défini)").gray()); + else + c.thenLegacyText(" \"" + ChatColor.RESET + result + ChatColor.RESET + "\""); + if (conflictMessage != null) + c.thenFailure(" " + conflictMessage); + DisplayTreeNode node = new DisplayTreeNode(c.get()); + + if (result == null && conflict == false && !inheritances.isEmpty()) { + // there is nothing interesting to show on current or subnode + node.children.add(new DisplayTreeNode(Chat.text("(Inheritances hidden for brevety)").darkGray().italic().get())); + return node; + } + + inheritances.forEach(n -> node.children.add(n.toDisplayTreeNode())); + + return node; + } + } + + private static class DataCacheKey { + final String name; + final EntityType type; + final DataType dataType; + DataCacheKey(String n, EntityType t, DataType d) { + name = n; type = t; dataType = d; + } + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof DataCacheKey)) + return false; + DataCacheKey o = (DataCacheKey) obj; + return Objects.equals(name, o.name) + && Objects.equals(type, o.type) + && dataType == o.dataType; + } + } + + private enum DataType { + PREFIX(CachedEntity::getSelfPrefix), + SUFFIX(CachedEntity::getSelfSuffix); + + private final CachedEntityGetter getter; + private DataType(CachedEntityGetter g) { + getter = g; + } + } + + private interface CachedEntityGetter { + R apply(CachedEntity a); + } + + + + + + + + private Cache effectivePermissionsCache = CacheBuilder.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .build(); + + /* package */ Boolean getEffectivePermission(String name, EntityType type, String permission, String server, String world) { + Preconditions.checkNotNull(name, "name can’t be null"); + Preconditions.checkNotNull(type, "type can’t be null"); + Preconditions.checkNotNull(permission, "permission can’t be null"); + Preconditions.checkArgument(world == null || server != null, "world not null but server is null"); + + boolean reversed = false; + if (permission.startsWith("-")) { + permission = permission.substring(1); + reversed = true; + } + + String fPermission = permission == null ? null : permission.toLowerCase(); + String fServer = server == null ? null : server.toLowerCase(); + String fWorld = world == null ? null : world.toLowerCase(); + try { + Boolean resolved = effectivePermissionsCache.get(new PermCacheKey(name, type, fPermission, fServer, fWorld), () -> { + return resolvePermission(name, type, fPermission, fServer, fWorld); + }).value; + return resolved == null ? null : (reversed != resolved.booleanValue()); + } catch (ExecutionException e) { + Log.severe(e); + return null; + } + } + + /* package */ DisplayTreeNode debugPermission(String name, EntityType type, String permission, String server, String world) { + CachedEntity entity = (type == EntityType.User) + ? backendReader.getCachedPlayer(UUID.fromString(name)) + : backendReader.getCachedGroup(name); + return resolvePermission(entity, permission, server, world, true).toDisplayTreeNode(); + } + + private PermState resolvePermission(String name, EntityType type, String permission, String server, String world) { + + CachedEntity entity = (type == EntityType.User) + ? backendReader.getCachedPlayer(UUID.fromString(name)) + : backendReader.getCachedGroup(name); + PermResolutionNode resolutionResult = resolvePermission(entity, permission, server, world, true); + + if (resolutionResult.conflict) { + Log.warning("For permission " + permission + ":"); + BaseComponent[] cmps = ChatUtil.treeView(resolutionResult.toDisplayTreeNode(), true); + for (BaseComponent cmp : cmps) + Log.warning(cmp.toLegacyText()); + } + + return resolutionResult.result; + } + + + + + + + private PermResolutionNode resolvePermission(CachedEntity entity, String permission, String server, String world, boolean checkInheritance) { + + // self and special permissions + PermResolutionNode resolutionNode = resolveSelfPermission(entity, permission, server, world); + if (resolutionNode.result != PermState.UNDEFINED) { + return resolutionNode; + } + + List inheritances = resolutionNode.entity instanceof CachedPlayer + ? ((CachedPlayer)resolutionNode.entity).groups + : ((CachedGroup)resolutionNode.entity).inheritances; + + + // check no-world/no-server permissions + if (server != null) { + PermResolutionNode noWNoSNode = resolvePermission(entity, permission, world != null ? server : null, null, false); + resolutionNode.inheritances.add(noWNoSNode); + if (noWNoSNode.conflict) + resolutionNode.conflict = true; + if (noWNoSNode.result != PermState.UNDEFINED) { + resolutionNode.result = noWNoSNode.result; + return resolutionNode; + } + } + + + if (!checkInheritance) + return resolutionNode; + + // check inheritances permissions + List inheritedResults = new ArrayList<>(inheritances.size() + 1); + + for (CachedGroup inherited : inheritances) { + inheritedResults.add(resolvePermission(inherited, permission, server, world, true)); + } + + resolutionNode.inheritances.addAll(inheritedResults); + + if (inheritedResults.stream().anyMatch(g -> g.conflict)) + resolutionNode.conflict = true; + + Set inheritedPermissions = inheritedResults.stream() + .map(g -> g.result) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(PermState.class))); + + boolean inheritancesGranted = inheritedPermissions.contains(PermState.GRANTED); + boolean inheritancesRevoqued = inheritedPermissions.contains(PermState.REVOQUED); + if (inheritancesGranted != inheritancesRevoqued) { + resolutionNode.result = inheritancesGranted ? PermState.GRANTED : PermState.REVOQUED; + } + else if (inheritancesGranted && inheritancesRevoqued) { + resolutionNode.conflictMessage = (resolutionNode.conflictMessage == null ? "" : (resolutionNode.conflictMessage + " ; ")) + + "Unsolvable conflict between inheritances"; + resolutionNode.conflict = true; + } + + return resolutionNode; + } + + + + + private PermResolutionNode resolveSelfPermission(CachedEntity entity, String permission, String server, String world) { + // special permissions + PermState result = PermState.UNDEFINED; + String conflict = null; + List foundPerms = null; + + /* + * Check for special permissions + */ + if (entity instanceof CachedPlayer) { + PermPlayer permP = new PermPlayer(((CachedPlayer) entity).playerId); + ParsedSelfPermission specialPerm = null; + if (permission.equals("pandacube.grade.isinstaff")) { + boolean res = permP.inheritsFromGroup("staff-base", true); + specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL); + conflict = "Special permission 'pandacube.grade.isinstaff' is deprecated. Use 'pandacube.inheritsfrom.' instead."; + } + else if (permission.startsWith("pandacube.grade.")) { + String group = permission.substring("pandacube.grade.".length()); + boolean res = permP.inheritsFromGroup(group, false); + specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL); + conflict = "Special permission 'pandacube.grade.' is deprecated. Use 'pandacube.ingroup.' instead."; + } + else if (permission.startsWith("pandacube.ingroup.")) { + String group = permission.substring("pandacube.ingroup.".length()); + boolean res = permP.inheritsFromGroup(group, false); + specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL); + } + else if (permission.startsWith("pandacube.inheritsfrom.")) { + String group = permission.substring("pandacube.inheritsfrom.".length()); + boolean res = permP.inheritsFromGroup(group, true); + specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL); + } + else if (permission.startsWith("pandacube.inserver.")) { + String testedServer = permission.substring("pandacube.inserver.".length()); + boolean res = testedServer.equals(server); + specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL); + } + else if (permission.startsWith("pandacube.inserverworld.")) { + String testedServerWorld = permission.substring("pandacube.inserverworld.".length()); + boolean res = server != null && world != null && testedServerWorld.equals(server + "." + world); + specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL); + } + + if (specialPerm != null) { + result = PermState.of(specialPerm.result); + foundPerms = new ArrayList<>(); + foundPerms.add(specialPerm); + } + } + + + + if (result == PermState.UNDEFINED) { + foundPerms = entity.getSelfPermissions(server, world).stream() + .map(p -> { + ParsedSelfPermission resNode = null; + if (p.equalsIgnoreCase(permission)) + resNode = new ParsedSelfPermission(p, true, PermType.EXPLICIT); + else if (p.equalsIgnoreCase("-" + permission)) + resNode = new ParsedSelfPermission(p, false, PermType.EXPLICIT); + else if (p.endsWith("*") && permission.startsWith(p.substring(0, p.length() - 1))) + resNode = new ParsedSelfPermission(p, true, PermType.WILDCARD); + else if (p.endsWith("*") && p.startsWith("-") && permission.startsWith(p.substring(1, p.length() - 1))) + resNode = new ParsedSelfPermission(p, false, PermType.WILDCARD); + return resNode; + }) + .filter(p -> p != null) + .collect(Collectors.toList()); + + boolean explicitGranted = foundPerms.stream() + .anyMatch(n -> n.type == PermType.EXPLICIT && n.result == Boolean.TRUE); + boolean explicitRevoqued = foundPerms.stream() + .anyMatch(n -> n.type == PermType.EXPLICIT && n.result == Boolean.FALSE); + + boolean wildcardGranted = foundPerms.stream() + .anyMatch(n -> n.type == PermType.WILDCARD && n.result == Boolean.TRUE); + boolean wildcardRevoqued = foundPerms.stream() + .anyMatch(n -> n.type == PermType.WILDCARD && n.result == Boolean.FALSE); + + if (explicitGranted != explicitRevoqued) { + result = PermState.of(explicitGranted); + if (!wildcardGranted && !wildcardRevoqued) { } + else if (wildcardGranted && wildcardRevoqued) { + conflict = "Self explicit permission defined but conflict between self wildcard permissions"; + } + else if (explicitGranted == wildcardGranted) { + conflict = "Unnecessary explicit permission already granted by self wildcard permissions"; // redundent explicit perm + } + } + else if (explicitGranted && explicitRevoqued) { + conflict = "Unsolvable conflit between explicit permissions"; + } + else if (wildcardGranted != wildcardRevoqued) { + result = PermState.of(wildcardGranted); + } + else if (wildcardGranted && wildcardRevoqued) { + conflict = "Unsolvable conflit between wildcard permissions"; + } + } + + PermResolutionNode node = new PermResolutionNode(entity, server, world, result, conflict); + node.selfPermissions = foundPerms; + + return node; + } + + private static class PermResolutionNode { + final CachedEntity entity; + final String server, world; + PermState result; + String conflictMessage; + boolean conflict; + List selfPermissions = new ArrayList<>(); + final List inheritances = new ArrayList<>(); + public PermResolutionNode(CachedEntity e, String s, String w, PermState r, String c) { + entity = e; server = s; world = w; result = r; conflictMessage = c; + conflict = c != null; + } + + public DisplayTreeNode toDisplayTreeNode() { + Chat c = Chat.chat() + .then(result == PermState.UNDEFINED ? Chat.dataText("■") : result == PermState.GRANTED ? Chat.successText("✔") : Chat.failureText("✘")) + .then(Chat.text(entity instanceof CachedPlayer ? PlayerFinder.getLastKnownName(((CachedPlayer)entity).playerId) : entity.name) + .color(entity instanceof CachedPlayer ? ChatColor.GOLD : ChatColor.DARK_AQUA) + ); + if (server != null) + c.thenData(" s=" + server); + if (world != null) + c.thenData(" w=" + world); + if (conflictMessage != null) + c.then(Chat.failureText(" " + conflictMessage)); + DisplayTreeNode node = new DisplayTreeNode(c.get()); + + selfPermissions.forEach(p -> node.children.add(p.toDisplayTreeNode())); + + if (result == PermState.UNDEFINED && conflict == false && !inheritances.isEmpty()) { + // there is nothing interesting to show on current or subnode + node.children.add(new DisplayTreeNode(Chat.text("(Inheritances hidden for brevety)").darkGray().italic().get())); + return node; + } + + inheritances.forEach(n -> node.children.add(n.toDisplayTreeNode())); + + return node; + } + } + + private static class ParsedSelfPermission { + final String permission; + final boolean result; + final PermType type; + public ParsedSelfPermission(String p, boolean r, PermType t) { + permission = p; + result = r; + type = t; + } + public DisplayTreeNode toDisplayTreeNode() { + return new DisplayTreeNode(Chat.chat() + .then(result ? Chat.successText("✔") : Chat.failureText("✘")) + .then(Chat.text(permission).color(type == PermType.WILDCARD ? ChatColor.YELLOW : type == PermType.SPECIAL ? ChatColor.LIGHT_PURPLE : ChatColor.WHITE)) + .get()); + } + } + + private static class PermCacheKey { + final String name; + final EntityType type; + final String permission, server, world; + public PermCacheKey(String n, EntityType t, String p, String s, String w) { + name = n; type = t; permission = p; server = s; world = w; + } + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof PermCacheKey)) + return false; + PermCacheKey o = (PermCacheKey) obj; + return Objects.equals(name, o.name) + && Objects.equals(type, o.type) + && Objects.equals(permission, o.permission) + && Objects.equals(server, o.server) + && Objects.equals(world, o.world); + } + } + + private enum PermType { + EXPLICIT, WILDCARD, SPECIAL; + } + + private enum PermState { + GRANTED(true), + REVOQUED(false), + UNDEFINED(null); + final Boolean value; + private PermState(Boolean v) { value = v; } + private static PermState of(Boolean v) { + return v == null ? UNDEFINED : v ? GRANTED : REVOQUED; + } + } + +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/permissions/SQLPermissions.java b/Core/src/main/java/fr/pandacube/lib/core/permissions/SQLPermissions.java new file mode 100644 index 0000000..f3b60b5 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/permissions/SQLPermissions.java @@ -0,0 +1,46 @@ +package fr.pandacube.lib.core.permissions; + +import fr.pandacube.lib.core.db.SQLElement; +import fr.pandacube.lib.core.db.SQLField; + +public class SQLPermissions extends SQLElement { + + public SQLPermissions() { + super(); + } + + public SQLPermissions(int id) { + super(id); + } + + @Override + protected String tableName() { + return "pandacube_permissions"; + } + + public static final SQLField name = field(VARCHAR(64), false); + public static final SQLField type = field(TINYINT, false); + public static final SQLField key = field(VARCHAR(256), false); + public static final SQLField value = field(VARCHAR(256), false); + public static final SQLField server = field(VARCHAR(64), true); + public static final SQLField world = field(VARCHAR(64), true); + + + public enum EntityType { + User, + Group; + + public int getCode() { + return ordinal(); + } + + public static EntityType getByCode(int code) { + if (code >= 0 && code < values().length) + return values()[code]; + return null; + } + } + + + +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/permissions/ServerWorldKey.java b/Core/src/main/java/fr/pandacube/lib/core/permissions/ServerWorldKey.java new file mode 100644 index 0000000..ecfb2de --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/permissions/ServerWorldKey.java @@ -0,0 +1,30 @@ +package fr.pandacube.lib.core.permissions; + +import java.util.Comparator; +import java.util.Objects; + +public class ServerWorldKey implements Comparable { + public final String server, world; + ServerWorldKey(String s, String w) { + server = s; world = w; + } + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ServerWorldKey)) + return false; + ServerWorldKey o = (ServerWorldKey) obj; + return Objects.equals(server, o.server) + && Objects.equals(world, o.world); + } + @Override + public int hashCode() { + return Objects.hash(world, server); + } + @Override + public int compareTo(ServerWorldKey o) { + Comparator compStrNullFirst = Comparator.nullsFirst(String::compareToIgnoreCase); + return Comparator.comparing((ServerWorldKey k) -> k.server, compStrNullFirst) + .thenComparing(k -> k.world, compStrNullFirst) + .compare(this, o); + } +} \ No newline at end of file diff --git a/Core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java b/Core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java new file mode 100644 index 0000000..5a84203 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java @@ -0,0 +1,247 @@ +package fr.pandacube.lib.core.players; + +import java.util.UUID; + +import fr.pandacube.lib.core.chat.ChatColorUtil; +import fr.pandacube.lib.core.db.DBException; +import fr.pandacube.lib.core.permissions.PermPlayer; +import fr.pandacube.lib.core.permissions.Permissions; +import fr.pandacube.lib.core.util.Log; + +public interface IOffPlayer { + + + + + /* + * General data and state + */ + + /** + * @return the id of the player + */ + public abstract UUID getUniqueId(); + + /** + * @return the last known player name of this player, or null if this player never joined the network. + */ + public default String getName() { + return PlayerFinder.getLastKnownName(getUniqueId()); + } + + /** + * Indicate if this player is connected to the current node (server or proxy, depending on interface implementation) + * @return wether the player is online or not + */ + public abstract boolean isOnline(); + + + + + /* + * Related class instances + */ + + /** + * Return the online instance of this player, if any exists. + * May return itself if the current instance already represent an online player. + */ + public abstract IOnlinePlayer getOnlineInstance(); + + /** + * Get the database entry of this player, or null if the player never joined the network. + * @throws DBException + */ + public default SQLPlayer getDbPlayer() throws DBException { + return SQLPlayer.getPlayerFromUUID(getUniqueId()); + } + + /** + * Get the permission instance of this player. This will never return null. + * @return the permission instance of this player + */ + public default PermPlayer getPermissionUser() { + return Permissions.getPlayer(getUniqueId()); + } + + + + + + + /* + * Display name + */ + + /** + * Returns the name of the player (if any), with eventual prefix and suffix depending on permission groups + * (and team for bukkit implementation) + * @return the display name of the player + */ + public abstract String getDisplayName(); + + /** + * Get an updated display name of the user, + * generated using eventual permission’s prefix(es) and suffix(es) of the player, + * and with color codes translated to Minecraft’s native {@code §}. + * @return + */ + public default String getDisplayNameFromPermissionSystem() { + PermPlayer permU = getPermissionUser(); + return ChatColorUtil.translateAlternateColorCodes('&', + permU.getPrefix() + getName() + permU.getSuffix()); + } + + + + + + + /* + * Permissions and groups + */ + + /** + * Tells if this player has the specified permission. + * If the player is online, this will redirect the + * method call to the {@link IOnlinePlayer} instance, + * that MUST override this current method to avoid recussive + * loop. + * If the player is offline, it just call the Pandacube + * permission system. + * @param permission the permission node to test + * @return whether this player has the provided permission + */ + public default boolean hasPermission(String permission) { + IOnlinePlayer online = getOnlineInstance(); + + if (online != null) + return online.hasPermission(permission); + + // at this point, the player is offline + Boolean res = getPermissionUser().hasPermission(permission); + return res != null ? res : false; + } + + /** + * Tells if the this player is part of the specified group + * + * @param group the permissions group + * @return true if this player is part of the group, + * false otherwise + */ + public default boolean isInGroup(String group) { + return getPermissionUser().isInGroup(group); + } + + /** + * Tells if this player is part of the staff, based on permission groups + */ + public default boolean isInStaff() { + return getPermissionUser().inheritsFromGroup("staff-base", true); + } + + + + + + + + /* + * Ignore + */ + + /** + * Tells if this player have the right to ignore the provided player + * @param ignored the player that is potentially ignored by this player. + * If this parameter is null, this method returns false. + */ + public default boolean canIgnore(IOffPlayer ignored) { + if (ignored == null) + return false; + if (equals(ignored)) + return false; + if (!isInStaff() && !ignored.isInStaff()) + return true; + return hasPermission("pandacube.ignore.bypassfor." + ignored.getUniqueId()); + } + + /** + * Tells if the provided player have the right to ignore this player + * @param ignorer the player that potentially ignore this player + * If this parameter is null, this method returns false. + * @implNote the default implementation just calls {@link #canIgnore(IOffPlayer) ignorer.canIgnore(this)}. + */ + public default boolean canBeIgnoredBy(IOffPlayer ignorer) { + if (ignorer == null) + return false; + return ignorer.canIgnore(this); + } + + /** + * Determine if this player ignore the provided player. + * @param ignored the player that is potentially ignored by this player. + * If this parameter is null, this method returns false. + * @return true if this player have to right to ignore the provided player and is actually ignoring him. + */ + public default boolean isIgnoring(IOffPlayer ignored) { + if (!canIgnore(ignored)) + return false; + + try { + return SQLPlayerIgnore.isPlayerIgnoringPlayer(getUniqueId(), ignored.getUniqueId()); + } catch (DBException e) { + Log.severe("Can't determine if a player ignore another player, because we can't access to the database", e); + return false; + } + } + + + /** + * Determine if the provided player ignore this player, taking into account the exception permissions. + * @param ignorer the player that potentially ignore this player + * If this parameter is null, this method returns false. + * @return true if the provided player have to right to ignore this player and is actually ignoring him. + * @implNote the default implementation just calls {@link #isIgnoring(IOffPlayer) ignorer.isIgnoring(this)}. + */ + public default boolean isIgnoredBy(IOffPlayer ignorer) { + return ignorer.isIgnoring(this); + } + + + + + + /* + * Modération + */ + + /** + * Retrieve the time when the player will be unmuted, or null if the player is not muted. + * @return the timestamp in millisecond of when the player will be unmuted + */ + public default Long getMuteTimeout() { + try { + Long muteTimeout = getDbPlayer().get(SQLPlayer.muteTimeout); + if (muteTimeout == null || muteTimeout <= System.currentTimeMillis()) + return null; + return muteTimeout; + } catch (DBException e) { + Log.severe(e); + return null; + } + } + + /** + * Tells if the player is currently muted, meaning that they cannot communicate + * through the chat or private messages. + * @return + */ + public default boolean isMuted() { + return getMuteTimeout() != null; + } + + + + +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/players/IOnlinePlayer.java b/Core/src/main/java/fr/pandacube/lib/core/players/IOnlinePlayer.java new file mode 100644 index 0000000..e97988c --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/players/IOnlinePlayer.java @@ -0,0 +1,300 @@ +package fr.pandacube.lib.core.players; + +import java.util.Locale; +import java.util.UUID; + +import fr.pandacube.lib.core.chat.Chat; +import fr.pandacube.lib.core.db.DBException; +import net.md_5.bungee.api.chat.BaseComponent; + +public interface IOnlinePlayer extends IOffPlayer { + + + + + /* + * General data and state + */ + + /** + * @return The current name of this player + * @implSpec The implementation is expected to call the environment API + * (Bukkit/Bungee) to get the name of the player. + */ + public abstract String getName(); + + public abstract String getServerName(); + + public abstract String getWorldName(); + + + + + /** + * @throws IllegalStateException if the player was not found in the database (should never happen) + * @throws DBException if a database access error occurs + */ + @Override + public default SQLPlayer getDbPlayer() throws DBException { + SQLPlayer p = SQLPlayer.getPlayerFromUUID(getUniqueId()); + if (p == null) + throw new IllegalStateException("The player was not found in the database: " + getUniqueId()); + return p; + } + + + + + + /* + * Related class instances + */ + + + + + + + + /* + * Permissions and groups + */ + + /** + * Tells if this online player has the specified permission. + * @implSpec the implementation of this method must not directly or + * indirectly call the method {@link IOffPlayer#hasPermission(String)}, + * or it may result in a {@link StackOverflowError}. + */ + public abstract boolean hasPermission(String permission); + + + + + + + + + + /* + * Vanish + */ + + public abstract boolean isVanished(); + + public default boolean isVanishedFor(IOffPlayer other) { + if (!isVanished()) + return false; // can see unvanished + + if (getUniqueId().equals(other.getUniqueId())) + return false; // can see themself + + if (!isInStaff() && other.isInStaff()) + return false; // can see non-staff as a staff + + if (other.hasPermission("pandacube.vanish.see." + getUniqueId())) + return false; // can see if has a specific permission + + return true; + } + + + + + + + + /* + * Sending packet and stuff to player + */ + + /** + * Display the provided message in the player’s chat, if + * the chat is activated. + * @param message the message to display. + */ + public abstract void sendMessage(BaseComponent message); + + /** + * Display the provided message in the player’s chat, if + * the chat is activated + * @param message the message to display + */ + public default void sendMessage(Chat message) { + sendMessage(message.get()); + } + + /** + * Display the provided message in the player’s chat, if + * they allows to display CHAT messages + * @param message the message to display. + * @param sender the player causing the send of this message. Client side filtering may occur. + * May be null if we don’t want client filtering, but still consider the message as CHAT message. + * @implNote implementation of this method should not filter the send of the message, based on + * the sender. This parameter is only there to be transmitted to the client, so client side filtering can + * be processed. + */ + public abstract void sendMessage(BaseComponent message, UUID sender); + + /** + * Display the provided message in the player’s chat, if + * they allows to display CHAT messages + * @param message the message to display + * @param sender the player causing the send of this message. Client side filtering may occur. + * May be null if we don’t want client filtering, but still consider the message as CHAT message. + */ + public default void sendMessage(Chat message, UUID sender) { + sendMessage(message.get(), sender); + } + + /** + * Display the provided message in the player’s chat, if the chat is + * activated, prepended with the server prefix. + * @param message the message to display + */ + public default void sendPrefixedMessage(BaseComponent message) { + sendMessage(IPlayerManager.prefixedAndColored(message)); + } + + /** + * Display the provided message in the player’s chat, if the chat is + * activated, prepended with the server prefix. + * @param message the message to display + */ + public default void sendPrefixedMessage(Chat message) { + sendPrefixedMessage(message.get()); + } + + /** + * Display a title in the middle of the screen. + * @param title The big text + * @param subtitle The less big text + * @param fadeIn Fade in time in tick + * @param stay Stay time in tick + * @param fadeOut Fade out time in tick + */ + public abstract void sendTitle(BaseComponent title, BaseComponent subtitle, int fadeIn, int stay, int fadeOut); + + /** + * Display a title in the middle of the screen. + * @param title The big text + * @param subtitle The less big text + * @param fadeIn Fade in time in tick + * @param stay Stay time in tick + * @param fadeOut Fade out time in tick + */ + public default void sendTitle(Chat title, Chat subtitle, int fadeIn, int stay, int fadeOut) { + sendTitle(title.get(), subtitle.get(), fadeIn, stay, fadeOut); + } + + /** + * Update the server brand field in the debug menu (F3) of the player + * (third line in 1.15 debug screen). Supports ChatColor codes but no + * line break. + * @param brand the server brand to send to the client. + */ + public abstract void sendServerBrand(String brand); + + + + + + + + + /* + * Client options + */ + + + + public abstract ClientOptions getClientOptions(); + + public interface ClientOptions { + + public Locale getLocale(); + + public int getViewDistance(); + + + + public boolean hasChatColorEnabled(); + + /** + * Tells if the client is configured to completely hide the chat to the + * player. When this is the case, nothing is displayed in the chat box, + * and the player can’t send any message or command. + * @implSpec if the value is unknown, it is assumed that the chat is + * fully visible. + */ + public boolean isChatHidden(); + + /** + * Tells if the client is configured to display the chat normally. + * When this is the case, chat messages and system messages are + * displayed in the chat box, and the player can send messages and + * commands. + * @implSpec if the value is unknown, it is assumed that the chat is + * fully visible. + */ + public boolean isChatFullyVisible(); + + /** + * Tells if the client is configured to only display system messages + * in the chat. + * When this is the case, chat messages are hidden but system messages + * are visible in the chat box, and the player can only send commands. + * @implSpec if the value is unknown, it is assumed that the chat is + * fully visible. + */ + public boolean isChatOnlyDisplayingSystemMessages(); + + + + /** + * Tells if the client has configured the main hand on the left. + * @implSpec if the value is unknown, it is assumed that the main hand + * is on the right. + */ + public boolean isLeftHanded(); + + /** + * Tells if the client has configured the main hand on the right. + * @implSpec if the value is unknown, it is assumed that the main hand + * is on the right. + */ + public boolean isRightHanded(); + + + + public boolean hasSkinCapeEnabled(); + + public boolean hasSkinJacketEnabled(); + + public boolean hasSkinLeftSleeveEnabled(); + + public boolean hasSkinRightSleeveEnabled(); + + public boolean hasSkinLeftPantsEnabled(); + + public boolean hasSkinRightPantsEnabled(); + + public boolean hasSkinHatsEnabled(); + + } + + /** + * Tells if the player can send chat messages or receive chat messages from + * other players, according to their client configuration. + *
+ * Chat messages represent public communication between players. By default, + * it only include actual chat message. This method may be used in commands + * like /me, /afk or the login/logout broadcasted messages + * @return + */ + public default boolean canChat() { + return getClientOptions().isChatFullyVisible(); + } + + +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/players/IPlayerManager.java b/Core/src/main/java/fr/pandacube/lib/core/players/IPlayerManager.java new file mode 100644 index 0000000..6c0fc5b --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/players/IPlayerManager.java @@ -0,0 +1,436 @@ +package fr.pandacube.lib.core.players; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import com.google.common.base.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import fr.pandacube.lib.core.chat.Chat; +import fr.pandacube.lib.core.db.DB; +import fr.pandacube.lib.core.db.DBInitTableException; +import fr.pandacube.lib.core.util.Log; +import net.md_5.bungee.api.chat.BaseComponent; + +public abstract class IPlayerManager { + private static IPlayerManager instance; + public static synchronized IPlayerManager getInstance() { return instance; } + + + private Map onlinePlayers = Collections.synchronizedMap(new HashMap<>()); + + private LoadingCache offlinePlayers = CacheBuilder.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) + .build(CacheLoader.from(pId -> newOffPlayerInstance(pId))); + + + public IPlayerManager() throws DBInitTableException { + synchronized (IPlayerManager.class) { + instance = this; + } + + DB.initTable(SQLPlayer.class); + DB.initTable(SQLPlayerNameHistory.class); + DB.initTable(SQLPlayerIgnore.class); + } + + + + protected void addPlayer(OP p) { + onlinePlayers.put(p.getUniqueId(), p); + offlinePlayers.invalidate(p.getUniqueId()); + } + + protected OP removePlayer(UUID p) { + return onlinePlayers.remove(p); + } + + public OP get(UUID p) { + return onlinePlayers.get(p); + } + + public boolean isOnline(UUID p) { + return onlinePlayers.containsKey(p); + } + + public int getPlayerCount() { + return onlinePlayers.size(); + } + + public List getAll() { + return new ArrayList<>(onlinePlayers.values()); + } + + public List getAllNotVanished() { + List players = getAll(); + players.removeIf(op -> op.isVanished()); + return players; + } + + public OF getOffline(UUID p) { + if (p == null) + return null; + OP online = get(p); + if (online != null) { + offlinePlayers.invalidate(p); + @SuppressWarnings("unchecked") + OF ret = (OF) online; + return ret; + } + // if not online + try { + return offlinePlayers.get(p); // load and cache new instance if necessary + } catch (Exception e) { + Log.severe("Cannot cache Offline player instance", e); + return newOffPlayerInstance(p); + } + } + + + + + protected abstract OF newOffPlayerInstance(UUID p); + + protected abstract void sendMessageToConsole(BaseComponent message); + + + + + + + + + + + + + + + + public static BaseComponent prefixedAndColored(BaseComponent message) { + return Chat.chat() + .color(Chat.getConfig().broadcastColor) + .then(Chat.getConfig().prefix.get()) + .then(message) + .get(); + } + + + + + /* + * Message broadcasting + */ + + // BaseComponent/Chat/String message + // boolean prefix + // boolean console = (permission == null) + // String permission = null + // UUID sourcePlayer = null + + + /** + * Broadcast a message to some or all players, and eventually to the console. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param console if the message must be displayed in the console. + * @param permission if not null, the message is only sent to player with this permission. + * @param sourcePlayer specifiy the eventual player that is the source of the message. + * If null, the message will be sent as a SYSTEM chat message. + * If not null, the message will be sent as a CHAT message, and will not be sent + * to players ignoring the provided player. + * + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(BaseComponent message, boolean prefix, boolean console, String permission, UUID sourcePlayer) { + Preconditions.checkNotNull(message, "message cannot be null"); + + IOffPlayer oSourcePlayer = getInstance().getOffline(sourcePlayer); + + if (prefix) + message = prefixedAndColored(message); + + for (IOnlinePlayer op : getInstance().getAll()) { + if (permission != null && !(op.hasPermission(permission))) continue; + if (sourcePlayer != null && op.isIgnoring(oSourcePlayer)) + continue; + + if (sourcePlayer != null) { + if (op.canIgnore(oSourcePlayer)) { + op.sendMessage(message, sourcePlayer); // CHAT message with UUID + } + else { + op.sendMessage(message, null); // CHAT message without UUID + } + } + else + op.sendMessage(message); // SYSTEM message + } + + if (console) + getInstance().sendMessageToConsole(message); + } + + /** + * Broadcast a message to some or all players, and eventually to the console. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(BaseComponent, boolean, boolean, String, UUID)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param console if the message must be displayed in the console. + * @param permission if not null, the message is only sent to player with this permission. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(BaseComponent message, boolean prefix, boolean console, String permission) { + broadcast(message, prefix, console, permission, null); + } + + /** + * Broadcast a message to all players, and eventually to the console. + *

+ * This method does not restrict the reception of the message to a specific permission. If you + * want to specify a permission, use {@link #broadcast(BaseComponent, boolean, boolean, String, UUID)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param console if the message must be displayed in the console. + * @param sourcePlayer specifiy the eventual player that is the source of the message. + * If null, the message will be sent as a SYSTEM chat message. + * If not null, the message will be sent as a CHAT message, and will not be sent + * to players ignoring the provided player. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(BaseComponent message, boolean prefix, boolean console, UUID sourcePlayer) { + broadcast(message, prefix, console, null, sourcePlayer); + } + + /** + * Broadcast a message to all players, and eventually to the console. + *

+ * This method does not restrict the reception of the message to a specific permission. If you + * want to specify a permission, use {@link #broadcast(BaseComponent, boolean, boolean, String)}. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(BaseComponent, boolean, boolean, UUID)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param console if the message must be displayed in the console. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(BaseComponent message, boolean prefix, boolean console) { + broadcast(message, prefix, console, null, null); + } + + /** + * Broadcast a message to some or all players, and eventually to the console. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(BaseComponent, boolean, String, UUID)}. + *

+ * This method decides to send the message to the console depending on whether {@code permission} + * is null (will send to console) or not (will not send to console). To specify this behaviour, use + * {@link #broadcast(BaseComponent, boolean, boolean, String)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param permission if not null, the message is only sent to player with this permission (but not to console). + * If null, the message will be sent to all players and to console. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(BaseComponent message, boolean prefix, String permission) { + broadcast(message, prefix, (permission == null), permission, null); + } + + /** + * Broadcast a message to all players, and to the console. + *

+ * This method does not restrict the reception of the message to a specific permission. If you + * want to specify a permission, use {@link #broadcast(BaseComponent, boolean, String, UUID)}. + *

+ * This method sends the message to the console. To change this behaviour, use + * {@link #broadcast(BaseComponent, boolean, boolean, UUID)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param sourcePlayer specifiy the eventual player that is the source of the message. + * If null, the message will be sent as a SYSTEM chat message. + * If not null, the message will be sent as a CHAT message, and will not be sent + * to players ignoring the provided player. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(BaseComponent message, boolean prefix, UUID sourcePlayer) { + broadcast(message, prefix, true, null, sourcePlayer); + } + + /** + * Broadcast a message to all players, and to the console. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(BaseComponent, boolean, UUID)}. + *

+ * This method does not restrict the reception of the message to a specific permission. If you + * want to specify a permission, use {@link #broadcast(BaseComponent, boolean, String)}. + *

+ * This method sends the message to the console. To change this behaviour, use + * {@link #broadcast(BaseComponent, boolean, boolean)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(BaseComponent message, boolean prefix) { + broadcast(message, prefix, true, null, null); + } + + + + + /** + * Broadcast a message to some or all players, and eventually to the console. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param console if the message must be displayed in the console. + * @param permission if not null, the message is only sent to player with this permission. + * @param sourcePlayer specifiy the eventual player that is the source of the message. + * If null, the message will be sent as a SYSTEM chat message. + * If not null, the message will be sent as a CHAT message, and will not be sent + * to players ignoring the provided player. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(Chat message, boolean prefix, boolean console, String permission, UUID sourcePlayer) { + Preconditions.checkNotNull(message, "message cannot be null"); + broadcast(message.get(), prefix, console, permission, sourcePlayer); + } + + /** + * Broadcast a message to some or all players, and eventually to the console. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(BaseComponent, boolean, boolean, String, UUID)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param console if the message must be displayed in the console. + * @param permission if not null, the message is only sent to player with this permission. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(Chat message, boolean prefix, boolean console, String permission) { + broadcast(message, prefix, console, permission, null); + } + + /** + * Broadcast a message to all players, and eventually to the console. + *

+ * This method does not restrict the reception of the message to a specific permission. If you + * want to specify a permission, use {@link #broadcast(BaseComponent, boolean, boolean, String, UUID)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param console if the message must be displayed in the console. + * @param sourcePlayer specifiy the eventual player that is the source of the message. + * If null, the message will be sent as a SYSTEM chat message. + * If not null, the message will be sent as a CHAT message, and will not be sent + * to players ignoring the provided player. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(Chat message, boolean prefix, boolean console, UUID sourcePlayer) { + broadcast(message, prefix, console, null, sourcePlayer); + } + + /** + * Broadcast a message to all players, and eventually to the console. + *

+ * This method does not restrict the reception of the message to a specific permission. If you + * want to specify a permission, use {@link #broadcast(BaseComponent, boolean, boolean, String)}. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(BaseComponent, boolean, boolean, UUID)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param console if the message must be displayed in the console. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(Chat message, boolean prefix, boolean console) { + broadcast(message, prefix, console, null, null); + } + + /** + * Broadcast a message to some or all players, and eventually to the console. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(BaseComponent, boolean, String, UUID)}. + *

+ * This method decides to send the message to the console depending on whether {@code permission} + * is null (will send to console) or not (will not send to console). To specify this behaviour, use + * {@link #broadcast(BaseComponent, boolean, boolean, String)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param permission if not null, the message is only sent to player with this permission (but not to console). + * If null, the message will be sent to all players and to console. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(Chat message, boolean prefix, String permission) { + broadcast(message, prefix, (permission == null), permission, null); + } + + /** + * Broadcast a message to all players, and to the console. + *

+ * This method does not restrict the reception of the message to a specific permission. If you + * want to specify a permission, use {@link #broadcast(BaseComponent, boolean, String, UUID)}. + *

+ * This method sends the message to the console. To change this behaviour, use + * {@link #broadcast(BaseComponent, boolean, boolean, UUID)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @param sourcePlayer specifiy the eventual player that is the source of the message. + * If null, the message will be sent as a SYSTEM chat message. + * If not null, the message will be sent as a CHAT message, and will not be sent + * to players ignoring the provided player. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(Chat message, boolean prefix, UUID sourcePlayer) { + broadcast(message, prefix, true, null, sourcePlayer); + } + + /** + * Broadcast a message to all players, and to the console. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(BaseComponent, boolean, UUID)}. + *

+ * This method does not restrict the reception of the message to a specific permission. If you + * want to specify a permission, use {@link #broadcast(BaseComponent, boolean, String)}. + *

+ * This method sends the message to the console. To change this behaviour, use + * {@link #broadcast(BaseComponent, boolean, boolean)}. + * + * @param message the message to send. + * @param prefix if the server prefix will be prepended to the message. + * @throws IllegalArgumentException if message is null. + */ + public static void broadcast(Chat message, boolean prefix) { + broadcast(message, prefix, true, null, null); + } + + + + + + +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/players/PlayerFinder.java b/Core/src/main/java/fr/pandacube/lib/core/players/PlayerFinder.java new file mode 100644 index 0000000..dced1c6 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/players/PlayerFinder.java @@ -0,0 +1,342 @@ +package fr.pandacube.lib.core.players; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.ToIntBiFunction; +import java.util.stream.Collectors; + +import org.javatuples.Pair; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.UncheckedExecutionException; + +import fr.pandacube.lib.core.commands.SuggestionsSupplier; +import fr.pandacube.lib.core.db.DB; +import fr.pandacube.lib.core.db.DBException; +import fr.pandacube.lib.core.db.SQLOrderBy; +import fr.pandacube.lib.core.util.LevenshteinDistance; +import fr.pandacube.lib.core.util.Log; + +/* + * Etape de recherche de joueur : + * utiliser directement la table pandacube_player + * chercher dans l'historique de login + */ +public class PlayerFinder { + + private static Cache playerLastKnownName = CacheBuilder.newBuilder() + .expireAfterWrite(2, TimeUnit.MINUTES) + .maximumSize(1000) + .build(); + + private static Cache, UUID> playerId = CacheBuilder.newBuilder() + .expireAfterWrite(2, TimeUnit.MINUTES) + .maximumSize(1000) + .build(); + + public static void clearCacheEntry(UUID pId, String pName) { + playerLastKnownName.invalidate(pId); + playerId.invalidate(Pair.with(pName.toLowerCase(), true)); + playerId.invalidate(Pair.with(pName.toLowerCase(), false)); + } + + public static String getLastKnownName(UUID id) { + if (id == null) return null; + + try { + return playerLastKnownName.get(id, () -> { + try { + return getDBPlayer(id).get(SQLPlayer.playerName); // eventual NPE will be ignored + } catch (NullPointerException|DBException e) { + Log.severe("Can't search for player name from uuid in database", e); + throw e; + } + }); + } catch (ExecutionException e) { + // ignored (ORM Exception) + } catch (UncheckedExecutionException e) { + Log.severe("Can’t retrieve player last known name of " + id, e); + } + + return null; + } + + /** + * Cherche un UUID de compte en se basant sur le pseudo passé en + * paramètre. La méthode + * cherchera d'abord dans les derniers pseudos connus. Puis, cherchera la + * dernière personne à + * s'être connecté avec ce pseudo sur le serveur. + * + * @param exactName le pseudo complet, insensible à la casse, et dans un + * format de pseudo valide + * @param old si on doit chercher dans les anciens pseudos de joueurs + * @return l'UUID du joueur si trouvé, null sinon + */ + public static UUID getPlayerId(String exactName, boolean old) { + if (!isValidPlayerName(exactName)) + return null; // évite une recherche inutile dans la base de donnée + + try { + return playerId.get(Pair.with(exactName.toLowerCase(), old), () -> { + try { + SQLPlayer el = DB.getFirst(SQLPlayer.class, + SQLPlayer.playerName.like(exactName.replace("_", "\\_")), + SQLOrderBy.desc(SQLPlayer.lastTimeInGame)); + /* + * Si il n'y a pas 1 élément, alors soit le pseudo n'a jamais été attribué + * soit il a été changé, et nous avons l'ancien possesseur et le nouveau possesseur du pseudo. + */ + if (el != null) + return el.get(SQLPlayer.playerId); + } catch (Exception e) { + Log.severe("Can't search for uuid from player name in database", e); + } + + if (old) { + try { + SQLPlayerNameHistory el = DB.getFirst(SQLPlayerNameHistory.class, + SQLPlayerNameHistory.playerName.like(exactName.replace("_", "\\_")), + SQLOrderBy.desc(SQLPlayerNameHistory.timeChanged)); + if (el != null) return el.get(SQLPlayerNameHistory.playerId); + } catch (Exception e) { + Log.severe("Can't search for uuid from old player name in database", e); + } + } + + throw new Exception(); // ignored + }); + } catch (ExecutionException e) { + // ignored + } + + return null; + + } + + /** + * Parse a player name or a player ID from the provided string, and returns the UUID of the player, if found. + * @param nameOrId a valid player name, or a UUID in the format of {@link UUID#toString()} + * @return the id of the player, or null if not found or if the input is invalid. + */ + public static UUID parsePlayer(String nameOrId) { + if (nameOrId == null) + return null; + if (isValidPlayerName(nameOrId)) + return getPlayerId(nameOrId, true); + try { + return UUID.fromString(nameOrId); + } catch (Exception e) { + return null; + } + } + + public static boolean isValidPlayerName(String name) { + if (name == null) return false; + return name.matches("[0-9a-zA-Z_]{2,16}"); + } + + public static SQLPlayer getDBPlayer(UUID id) throws Exception { + if (id == null) return null; + return SQLPlayer.getPlayerFromUUID(id); + } + + + + + + + + private static final SuggestionsSupplier TAB_PLAYER_OFFLINE = (sender, tokenIndex, token, args) -> { + if (token.length() < 3) { + return Collections.emptyList(); + } + List list = findPlayer(token, 10).profiles; + if (!list.isEmpty() && list.get(0).d == 0) + return Collections.singletonList(list.get(0).name); + return list.stream().map(p -> p.name).collect(Collectors.toList()); + }; + + @SuppressWarnings("unchecked") + public static final SuggestionsSupplier TAB_PLAYER_OFFLINE() { + return (SuggestionsSupplier) TAB_PLAYER_OFFLINE; + } + + + public static SearchResponse findPlayer(String query, int resultsCount) { + SearchResponse cacheData = searchCache.getUnchecked(query.toLowerCase()); + cacheData = new SearchResponse(cacheData.profiles.subList(0, Math.min(resultsCount, cacheData.profiles.size()))); + return cacheData; + } + + + public static int SEARCH_MAX_DISTANCE = 20; + public static int MISSING_CHAR_DISTANCE = 1; + public static int SURPLUS_CHAR_DISTANCE = 8; + public static int DIFF_CHAR_DISTANCE = 8; + public static int CLOSE_CHAR_DISTANCE = 4; + + public static int OLD_NICK_MULTIPLIER = 2; + + + private static List> CONFUSABLE_CHARACTERS = ImmutableList.of( + ImmutableList.of('o', '0'), + ImmutableList.of('i', '1', 'l'), + ImmutableList.of('b', '8') + ); + private static ToIntBiFunction CHAR_DISTANCE = (c1, c2) -> { + if (c1.equals(c2)) + return 0; + for (List charTab : CONFUSABLE_CHARACTERS) { + if (charTab.contains(c1) && charTab.contains(c2)) + return CLOSE_CHAR_DISTANCE; + } + return DIFF_CHAR_DISTANCE; + }; + + private static LoadingCache>> namesCache = CacheBuilder.newBuilder() + .expireAfterWrite(2, TimeUnit.MINUTES) + .maximumSize(1) + .build(CacheLoader.from((String k) -> { + List> cached = new ArrayList<>(); + try { + DB.forEach(SQLPlayerNameHistory.class, el -> { + cached.add(Pair.with(el.get(SQLPlayerNameHistory.playerName), el.get(SQLPlayerNameHistory.playerId))); + }); + } catch (DBException e) { + throw new RuntimeException(e); + } + return cached; + })); + + private static LoadingCache searchCache = CacheBuilder.newBuilder() + .expireAfterWrite(2, TimeUnit.MINUTES) + .maximumSize(100) + .build(CacheLoader.from((String query) -> { + List foundNames = new ArrayList<>(); + try { + namesCache.get("").forEach(el -> { + String name = el.getValue0(); + int dist = new LevenshteinDistance(name.toLowerCase(), query, SURPLUS_CHAR_DISTANCE, MISSING_CHAR_DISTANCE, CHAR_DISTANCE).getCurrentDistance(); + if (dist <= SEARCH_MAX_DISTANCE) { + FoundName n = new FoundName(); + n.dist = dist; + n.id = el.getValue1(); + n.name = name; + foundNames.add(n); + } + }); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + + Map profiles = new HashMap<>(); + + foundNames.forEach(foundName -> { + SearchResponseProfile profile = profiles.getOrDefault(foundName.id, new SearchResponseProfile()); + if (profile.id == null) { + profile.id = foundName.id.toString(); + profile.names = new ArrayList<>(); + profiles.put(foundName.id, profile); + } + profile.names.add(foundName); + }); + + try { + DB.forEach(SQLPlayer.class, SQLPlayer.playerId.in(profiles.keySet()), el -> { + SearchResponseProfile profile = profiles.get(el.get(SQLPlayer.playerId)); + if (profile == null) + return; + profile.displayName = el.get(SQLPlayer.playerDisplayName); + profile.name = el.get(SQLPlayer.playerName); + FoundName currentName = null; + for (FoundName foundName : profile.names) { + if (foundName.name.equals(profile.name)) { + currentName = foundName; + profile.d = foundName.dist; + break; + } + } + + if (currentName != null) { + profile.names.remove(currentName); + } + else { + int min = Integer.MAX_VALUE; + for (FoundName foundName : profile.names) { + if (foundName.dist < min) { + min = foundName.dist; + } + } + profile.d = min * OLD_NICK_MULTIPLIER + 1; + + if (profile.d > SEARCH_MAX_DISTANCE) + profiles.remove(el.get(SQLPlayer.playerId)); + } + + // unset id field in old names entries to save memory and network activity + profile.names.forEach(n -> n.id = null); + }); + } catch (DBException e) { + throw new RuntimeException(e); + } + + + List searchResponseList = new ArrayList<>(profiles.values()); + searchResponseList.sort(null); + + searchResponseList.removeIf(p -> { + if (p.name == null) { // if the current name was not found in the database + Log.warning("Cannot find current name for player " + p.id, new Throwable()); + return true; + } + return false; + }); + + return new SearchResponse(searchResponseList); + })); + + + public static class SearchResponseProfile implements Comparable { + public int d; + public String id; + public String name; + public String displayName; + public List names; + + @Override + public int compareTo(SearchResponseProfile o) { + return Integer.compare(d, o.d); + } + } + + private static class FoundName { + public UUID id; + public String name; + public int dist; + } + + + + public static class SearchResponse { + public final List profiles; + private SearchResponse(List p) { + profiles = p; + } + } + + + + + +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/players/SQLPlayer.java b/Core/src/main/java/fr/pandacube/lib/core/players/SQLPlayer.java new file mode 100644 index 0000000..b28e2cf --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/players/SQLPlayer.java @@ -0,0 +1,78 @@ +package fr.pandacube.lib.core.players; + +import java.sql.Date; +import java.util.Set; +import java.util.UUID; + +import fr.pandacube.lib.core.db.DB; +import fr.pandacube.lib.core.db.DBException; +import fr.pandacube.lib.core.db.SQLElement; +import fr.pandacube.lib.core.db.SQLElementList; +import fr.pandacube.lib.core.db.SQLField; + +public class SQLPlayer extends SQLElement { + + public SQLPlayer() { + super(); + } + + public SQLPlayer(int id) { + super(id); + } + + /* + * Nom de la table + */ + @Override + protected String tableName() { + return "pandacube_player"; + } + + /* + * Champs de la table + */ + public static final SQLField playerId = field(CHAR36_UUID, false); + public static final SQLField playerName = field(VARCHAR(16), false); + public static final SQLField token = field(CHAR36_UUID, true); + public static final SQLField mailCheck = field(VARCHAR(255), true); + public static final SQLField password = field(VARCHAR(255), true); + public static final SQLField mail = field(VARCHAR(255), true); + public static final SQLField playerDisplayName = field(VARCHAR(255), + false); + public static final SQLField firstTimeInGame = field(BIGINT, false, 0L); + public static final SQLField timeWebRegister = field(BIGINT, true); + public static final SQLField lastTimeInGame = field(BIGINT, true); + public static final SQLField lastWebActivity = field(BIGINT, false, 0L); + public static final SQLField onlineInServer = field(VARCHAR(32), true); + public static final SQLField skinURL = field(VARCHAR(255), true); + public static final SQLField isVanish = field(BOOLEAN, false, + (Boolean) false); + public static final SQLField birthday = field(DATE, true); + public static final SQLField lastYearCelebratedBirthday = field(INT, + false, 0); + public static final SQLField banTimeout = field(BIGINT, true); + public static final SQLField muteTimeout = field(BIGINT, true); + public static final SQLField isWhitelisted = field(BOOLEAN, false, + (Boolean) false); + public static final SQLField bambou = field(BIGINT, false, 0L); + public static final SQLField grade = field(VARCHAR(36), false, "default"); + + + + public static SQLPlayer getPlayerFromUUID(UUID pId) throws DBException { + if (pId == null) + return null; + return DB.getFirst(SQLPlayer.class, playerId.eq(pId)); + } + + + public static SQLElementList getPlayersFromUUIDs(Set playerIds) throws DBException { + + if (playerIds == null || playerIds.isEmpty()) { + return new SQLElementList<>(); + } + + return DB.getAll(SQLPlayer.class, playerId.in(playerIds)); + + } +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerIgnore.java b/Core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerIgnore.java new file mode 100644 index 0000000..c6d6307 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerIgnore.java @@ -0,0 +1,59 @@ +package fr.pandacube.lib.core.players; + +import java.util.Map; +import java.util.UUID; + +import fr.pandacube.lib.core.db.DB; +import fr.pandacube.lib.core.db.DBException; +import fr.pandacube.lib.core.db.SQLElement; +import fr.pandacube.lib.core.db.SQLFKField; + +public class SQLPlayerIgnore extends SQLElement { + + public SQLPlayerIgnore() { + super(); + } + + public SQLPlayerIgnore(int id) { + super(id); + } + + @Override + protected String tableName() { + return "pandacube_player_ignore"; + } + + public static final SQLFKField ignorer = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId); + public static final SQLFKField ignored = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId); + + + public static SQLPlayerIgnore getPlayerIgnoringPlayer(UUID ignorer, UUID ignored) throws DBException { + return DB.getFirst(SQLPlayerIgnore.class, SQLPlayerIgnore.ignorer.eq(ignorer).and(SQLPlayerIgnore.ignored.eq(ignored))); + } + + public static boolean isPlayerIgnoringPlayer(UUID ignorer, UUID ignored) throws DBException { + return getPlayerIgnoringPlayer(ignorer, ignored) != null; + } + + public static void setPlayerIgnorePlayer(UUID ignorer, UUID ignored, boolean newIgnoreState) throws DBException { + SQLPlayerIgnore el = getPlayerIgnoringPlayer(ignorer, ignored); + if (el == null && newIgnoreState) { + el = new SQLPlayerIgnore(); + el.set(SQLPlayerIgnore.ignorer, ignorer); + el.set(SQLPlayerIgnore.ignored, ignored); + el.save(); + return; + } + if (el != null && !newIgnoreState) { + el.delete(); + return; + } + + } + + public static Map getIgnoredPlayer(UUID ignorer) throws DBException { + return DB.getAll(SQLPlayerIgnore.class, SQLPlayerIgnore.ignorer.eq(ignorer)) + .getReferencedEntriesInGroups(SQLPlayerIgnore.ignored); + } + +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerNameHistory.java b/Core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerNameHistory.java new file mode 100644 index 0000000..bcfc755 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerNameHistory.java @@ -0,0 +1,53 @@ +package fr.pandacube.lib.core.players; + +import java.util.UUID; + +import fr.pandacube.lib.core.db.DB; +import fr.pandacube.lib.core.db.DBException; +import fr.pandacube.lib.core.db.SQLElement; +import fr.pandacube.lib.core.db.SQLFKField; +import fr.pandacube.lib.core.db.SQLField; +import fr.pandacube.lib.core.util.Log; + +public class SQLPlayerNameHistory extends SQLElement { + + public SQLPlayerNameHistory() { + super(); + } + + public SQLPlayerNameHistory(int id) { + super(id); + } + + @Override + protected String tableName() { + return "pandacube_player_name_history"; + } + + public static final SQLFKField playerId = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId); + public static final SQLField playerName = field(VARCHAR(16), false); + public static final SQLField timeChanged = field(BIGINT, true); + + + public static void updateIfNeeded(UUID player, String name, long time) { + SQLPlayerNameHistory histEl; + try { + histEl = DB.getFirst(SQLPlayerNameHistory.class, playerId.eq(player).and(playerName.eq(name))); + + if (histEl == null) { + histEl = new SQLPlayerNameHistory(); + histEl.set(playerId, player); + histEl.set(playerName, name); + histEl.set(timeChanged, time); + histEl.save(); + } + else if (time < histEl.get(timeChanged)) { + histEl.set(timeChanged, time); + histEl.save(); + } + } catch (DBException e) { + Log.severe(e); + } + } + +} diff --git a/Core/src/main/java/fr/pandacube/util/search/SearchEngine.java b/Core/src/main/java/fr/pandacube/lib/core/search/SearchEngine.java similarity index 98% rename from Core/src/main/java/fr/pandacube/util/search/SearchEngine.java rename to Core/src/main/java/fr/pandacube/lib/core/search/SearchEngine.java index 47e2279..2a340c4 100644 --- a/Core/src/main/java/fr/pandacube/util/search/SearchEngine.java +++ b/Core/src/main/java/fr/pandacube/lib/core/search/SearchEngine.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.search; +package fr.pandacube.lib.core.search; import java.util.ArrayList; import java.util.HashMap; @@ -14,7 +14,7 @@ import com.google.common.base.Preconditions; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import fr.pandacube.util.Log; +import fr.pandacube.lib.core.util.Log; /** * Utility class to manage searching among a set of diff --git a/Core/src/main/java/fr/pandacube/util/search/SearchResult.java b/Core/src/main/java/fr/pandacube/lib/core/search/SearchResult.java similarity index 79% rename from Core/src/main/java/fr/pandacube/util/search/SearchResult.java rename to Core/src/main/java/fr/pandacube/lib/core/search/SearchResult.java index fc02b52..4ce6d7d 100644 --- a/Core/src/main/java/fr/pandacube/util/search/SearchResult.java +++ b/Core/src/main/java/fr/pandacube/lib/core/search/SearchResult.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.search; +package fr.pandacube.lib.core.search; import java.util.Set; diff --git a/Core/src/main/java/fr/pandacube/util/AmountPerTimeLimiter.java b/Core/src/main/java/fr/pandacube/lib/core/util/AmountPerTimeLimiter.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/AmountPerTimeLimiter.java rename to Core/src/main/java/fr/pandacube/lib/core/util/AmountPerTimeLimiter.java index 8c5b687..7943bc3 100644 --- a/Core/src/main/java/fr/pandacube/util/AmountPerTimeLimiter.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/AmountPerTimeLimiter.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.util.ArrayList; import java.util.List; diff --git a/Core/src/main/java/fr/pandacube/util/BiMap.java b/Core/src/main/java/fr/pandacube/lib/core/util/BiMap.java similarity index 98% rename from Core/src/main/java/fr/pandacube/util/BiMap.java rename to Core/src/main/java/fr/pandacube/lib/core/util/BiMap.java index 7da87c7..6109d3a 100644 --- a/Core/src/main/java/fr/pandacube/util/BiMap.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/BiMap.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.util.Collections; import java.util.HashMap; diff --git a/Core/src/main/java/fr/pandacube/util/Callback.java b/Core/src/main/java/fr/pandacube/lib/core/util/Callback.java similarity index 69% rename from Core/src/main/java/fr/pandacube/util/Callback.java rename to Core/src/main/java/fr/pandacube/lib/core/util/Callback.java index ea82732..697a5e8 100644 --- a/Core/src/main/java/fr/pandacube/util/Callback.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/Callback.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; @FunctionalInterface public interface Callback { diff --git a/Core/src/main/java/fr/pandacube/util/measurement/DistanceUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/DistanceUtil.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/measurement/DistanceUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/util/DistanceUtil.java index 07ae91c..c800ca8 100644 --- a/Core/src/main/java/fr/pandacube/util/measurement/DistanceUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/DistanceUtil.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.measurement; +package fr.pandacube.lib.core.util; import java.text.DecimalFormat; import java.util.Arrays; diff --git a/Core/src/main/java/fr/pandacube/util/EnumUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/EnumUtil.java similarity index 98% rename from Core/src/main/java/fr/pandacube/util/EnumUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/util/EnumUtil.java index f253e88..4a2abe1 100644 --- a/Core/src/main/java/fr/pandacube/util/EnumUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/EnumUtil.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; public class EnumUtil { diff --git a/Core/src/main/java/fr/pandacube/lib/core/util/FileUtils.java b/Core/src/main/java/fr/pandacube/lib/core/util/FileUtils.java new file mode 100644 index 0000000..8010e44 --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/util/FileUtils.java @@ -0,0 +1,32 @@ +package fr.pandacube.lib.core.util; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class FileUtils { + + + + public static void delete(File target) { + if (target.isDirectory()) + for (File child : target.listFiles()) + delete(child); + target.delete(); + } + + + public static void copy(File source, File target) throws IOException { + if (target.exists() && !target.isDirectory()) { + throw new IllegalStateException("target file already exists: " + target); + } + if (source.isDirectory()) { + target.mkdir(); + for (String child : source.list()) + copy(new File(source, child), new File(target, child)); + } + else if (source.isFile()) { + Files.copy(source.toPath(), target.toPath()); + } + } +} diff --git a/Core/src/main/java/fr/pandacube/util/GifDecoder.java b/Core/src/main/java/fr/pandacube/lib/core/util/GifDecoder.java similarity index 99% rename from Core/src/main/java/fr/pandacube/util/GifDecoder.java rename to Core/src/main/java/fr/pandacube/lib/core/util/GifDecoder.java index d383c17..eed54d6 100644 --- a/Core/src/main/java/fr/pandacube/util/GifDecoder.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/GifDecoder.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.net.URL; import java.util.ArrayList; diff --git a/Core/src/main/java/fr/pandacube/util/JArithmeticInterpreter.java b/Core/src/main/java/fr/pandacube/lib/core/util/JArithmeticInterpreter.java similarity index 99% rename from Core/src/main/java/fr/pandacube/util/JArithmeticInterpreter.java rename to Core/src/main/java/fr/pandacube/lib/core/util/JArithmeticInterpreter.java index fb89ae3..e38123b 100644 --- a/Core/src/main/java/fr/pandacube/util/JArithmeticInterpreter.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/JArithmeticInterpreter.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; //****************************************************************************** //*** diff --git a/Core/src/main/java/fr/pandacube/util/LevenshteinDistance.java b/Core/src/main/java/fr/pandacube/lib/core/util/LevenshteinDistance.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/LevenshteinDistance.java rename to Core/src/main/java/fr/pandacube/lib/core/util/LevenshteinDistance.java index a5566c3..17fef77 100644 --- a/Core/src/main/java/fr/pandacube/util/LevenshteinDistance.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/LevenshteinDistance.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.util.Objects; import java.util.function.ToIntBiFunction; diff --git a/Core/src/main/java/fr/pandacube/util/ListUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/ListUtil.java similarity index 84% rename from Core/src/main/java/fr/pandacube/util/ListUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/util/ListUtil.java index 45578ef..f4ce7c7 100644 --- a/Core/src/main/java/fr/pandacube/util/ListUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/ListUtil.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.util.List; diff --git a/Core/src/main/java/fr/pandacube/util/Log.java b/Core/src/main/java/fr/pandacube/lib/core/util/Log.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/Log.java rename to Core/src/main/java/fr/pandacube/lib/core/util/Log.java index 04215a8..b082532 100644 --- a/Core/src/main/java/fr/pandacube/util/Log.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/Log.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; diff --git a/Core/src/main/java/fr/pandacube/util/MappedListView.java b/Core/src/main/java/fr/pandacube/lib/core/util/MappedListView.java similarity index 98% rename from Core/src/main/java/fr/pandacube/util/MappedListView.java rename to Core/src/main/java/fr/pandacube/lib/core/util/MappedListView.java index 745bdea..b143f83 100644 --- a/Core/src/main/java/fr/pandacube/util/MappedListView.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/MappedListView.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.util.AbstractList; import java.util.List; diff --git a/Core/src/main/java/fr/pandacube/util/measurement/MemoryUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/MemoryUtil.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/measurement/MemoryUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/util/MemoryUtil.java index 53c1358..e17587c 100644 --- a/Core/src/main/java/fr/pandacube/util/measurement/MemoryUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/MemoryUtil.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.measurement; +package fr.pandacube.lib.core.util; import java.text.DecimalFormat; diff --git a/Core/src/main/java/fr/pandacube/util/MinecraftVersion.java b/Core/src/main/java/fr/pandacube/lib/core/util/MinecraftVersion.java similarity index 99% rename from Core/src/main/java/fr/pandacube/util/MinecraftVersion.java rename to Core/src/main/java/fr/pandacube/lib/core/util/MinecraftVersion.java index 20f203c..42aefc5 100644 --- a/Core/src/main/java/fr/pandacube/util/MinecraftVersion.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/MinecraftVersion.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.util.ArrayList; import java.util.Arrays; diff --git a/Core/src/main/java/fr/pandacube/util/MinecraftWebUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/MinecraftWebUtil.java similarity index 91% rename from Core/src/main/java/fr/pandacube/util/MinecraftWebUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/util/MinecraftWebUtil.java index bf0a0f3..1a10f06 100644 --- a/Core/src/main/java/fr/pandacube/util/MinecraftWebUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/MinecraftWebUtil.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; public class MinecraftWebUtil { @@ -10,6 +10,8 @@ public class MinecraftWebUtil { @param */ + // TODO update to support RGB colors (Bungee and Essentials notation). + // See JavaScript implementation at https://www.pandacube.fr/assets/js/global.js public static String fromMinecraftColorCodeToHTML(char code_prefix, String ligne) { String color_char = "0123456789abcdefr"; diff --git a/Core/src/main/java/fr/pandacube/util/OfflineUUID.java b/Core/src/main/java/fr/pandacube/lib/core/util/OfflineUUID.java similarity index 95% rename from Core/src/main/java/fr/pandacube/util/OfflineUUID.java rename to Core/src/main/java/fr/pandacube/lib/core/util/OfflineUUID.java index 008e26b..fa94f0d 100644 --- a/Core/src/main/java/fr/pandacube/util/OfflineUUID.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/OfflineUUID.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.nio.charset.Charset; import java.util.UUID; diff --git a/Core/src/main/java/fr/pandacube/util/RandomUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/RandomUtil.java similarity index 96% rename from Core/src/main/java/fr/pandacube/util/RandomUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/util/RandomUtil.java index 19e436c..8262be3 100644 --- a/Core/src/main/java/fr/pandacube/util/RandomUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/RandomUtil.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.util.List; import java.util.Random; diff --git a/Core/src/main/java/fr/pandacube/util/ReflexionUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/ReflexionUtil.java similarity index 99% rename from Core/src/main/java/fr/pandacube/util/ReflexionUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/util/ReflexionUtil.java index a07474c..1c9b680 100644 --- a/Core/src/main/java/fr/pandacube/util/ReflexionUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/ReflexionUtil.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.lang.reflect.Field; import java.lang.reflect.Method; diff --git a/Core/src/main/java/fr/pandacube/util/ServerPropertyFile.java b/Core/src/main/java/fr/pandacube/lib/core/util/ServerPropertyFile.java similarity index 98% rename from Core/src/main/java/fr/pandacube/util/ServerPropertyFile.java rename to Core/src/main/java/fr/pandacube/lib/core/util/ServerPropertyFile.java index d2a9d76..af30a25 100644 --- a/Core/src/main/java/fr/pandacube/util/ServerPropertyFile.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/ServerPropertyFile.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.io.BufferedReader; import java.io.BufferedWriter; diff --git a/Core/src/main/java/fr/pandacube/util/StringUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/StringUtil.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/StringUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/util/StringUtil.java index b9f6faa..8701942 100644 --- a/Core/src/main/java/fr/pandacube/util/StringUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/StringUtil.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.util.List; diff --git a/Core/src/main/java/fr/pandacube/util/ThrowableUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/ThrowableUtil.java similarity index 93% rename from Core/src/main/java/fr/pandacube/util/ThrowableUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/util/ThrowableUtil.java index e0b161c..6c237ac 100644 --- a/Core/src/main/java/fr/pandacube/util/ThrowableUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/ThrowableUtil.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/Core/src/main/java/fr/pandacube/util/measurement/Tick.java b/Core/src/main/java/fr/pandacube/lib/core/util/Tick.java similarity index 93% rename from Core/src/main/java/fr/pandacube/util/measurement/Tick.java rename to Core/src/main/java/fr/pandacube/lib/core/util/Tick.java index 373ff2c..544c1ab 100644 --- a/Core/src/main/java/fr/pandacube/util/measurement/Tick.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/Tick.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.measurement; +package fr.pandacube.lib.core.util; public class Tick { diff --git a/Core/src/main/java/fr/pandacube/util/measurement/TimeUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/TimeUtil.java similarity index 95% rename from Core/src/main/java/fr/pandacube/util/measurement/TimeUtil.java rename to Core/src/main/java/fr/pandacube/lib/core/util/TimeUtil.java index ad52218..f12cc9e 100644 --- a/Core/src/main/java/fr/pandacube/util/measurement/TimeUtil.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/TimeUtil.java @@ -1,4 +1,4 @@ -package fr.pandacube.util.measurement; +package fr.pandacube.lib.core.util; import java.time.Instant; import java.time.LocalDateTime; @@ -9,7 +9,9 @@ import java.util.Calendar; import java.util.Collections; import java.util.GregorianCalendar; import java.util.List; +import java.util.Locale; import java.util.Objects; +import java.util.TimeZone; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; @@ -17,22 +19,20 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import fr.pandacube.Pandacube; -import fr.pandacube.util.StringUtil; -import fr.pandacube.util.commands.SuggestionsSupplier; +import fr.pandacube.lib.core.commands.SuggestionsSupplier; public class TimeUtil { - private static DateTimeFormatter cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Pandacube.LOCALE); - private static DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Pandacube.LOCALE); - private static DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Pandacube.LOCALE); - private static DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Pandacube.LOCALE); - private static DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Pandacube.LOCALE); - private static DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Pandacube.LOCALE); + private static DateTimeFormatter cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Locale.getDefault()); + private static DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Locale.getDefault()); + private static DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Locale.getDefault()); + private static DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Locale.getDefault()); + private static DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Locale.getDefault()); + private static DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Locale.getDefault()); - private static DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Pandacube.LOCALE); - private static DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Pandacube.LOCALE); + private static DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.getDefault()); + private static DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Locale.getDefault()); @@ -124,7 +124,7 @@ public class TimeUtil { private static LocalDateTime toLocalDateTime(long msTime) { - return Instant.ofEpochMilli(msTime).atZone(Pandacube.TIMEZONE.toZoneId()).toLocalDateTime(); + return Instant.ofEpochMilli(msTime).atZone(TimeZone.getDefault().toZoneId()).toLocalDateTime(); } diff --git a/Core/src/main/java/fr/pandacube/util/TypeConverter.java b/Core/src/main/java/fr/pandacube/lib/core/util/TypeConverter.java similarity index 97% rename from Core/src/main/java/fr/pandacube/util/TypeConverter.java rename to Core/src/main/java/fr/pandacube/lib/core/util/TypeConverter.java index b420681..ef0df57 100644 --- a/Core/src/main/java/fr/pandacube/util/TypeConverter.java +++ b/Core/src/main/java/fr/pandacube/lib/core/util/TypeConverter.java @@ -1,4 +1,4 @@ -package fr.pandacube.util; +package fr.pandacube.lib.core.util; import java.util.ArrayList; import java.util.HashMap; @@ -49,7 +49,7 @@ public class TypeConverter { public static int toPrimInt(Object o) { Integer val = toInteger(o); if (val == null) - throw new ConvertionException("null values can't converted to primitive int"); + throw new ConvertionException("null values can't be converted to primitive int"); return val; } diff --git a/Core/src/main/java/fr/pandacube/util/DirUtils.java b/Core/src/main/java/fr/pandacube/util/DirUtils.java deleted file mode 100644 index 4268024..0000000 --- a/Core/src/main/java/fr/pandacube/util/DirUtils.java +++ /dev/null @@ -1,15 +0,0 @@ -package fr.pandacube.util; - -import java.io.File; - -public class DirUtils { - - - - public static void delete(final File target) { - if (target.isDirectory()) - for (File child : target.listFiles()) - delete(child); - target.delete(); - } -} diff --git a/Core/src/main/java/fr/pandacube/util/orm/ORMException.java b/Core/src/main/java/fr/pandacube/util/orm/ORMException.java deleted file mode 100644 index fa7f9c2..0000000 --- a/Core/src/main/java/fr/pandacube/util/orm/ORMException.java +++ /dev/null @@ -1,18 +0,0 @@ -package fr.pandacube.util.orm; - -public class ORMException extends Exception { - private static final long serialVersionUID = 1L; - - public ORMException(Throwable initCause) { - super(initCause); - } - - public ORMException(String message, Throwable initCause) { - super(message, initCause); - } - - public ORMException(String message) { - super(message); - } - -} diff --git a/Core/src/main/java/fr/pandacube/util/orm/ORMInitTableException.java b/Core/src/main/java/fr/pandacube/util/orm/ORMInitTableException.java deleted file mode 100644 index 7e0f791..0000000 --- a/Core/src/main/java/fr/pandacube/util/orm/ORMInitTableException.java +++ /dev/null @@ -1,14 +0,0 @@ -package fr.pandacube.util.orm; - -public class ORMInitTableException extends ORMException { - private static final long serialVersionUID = 1L; - - /* package */ > ORMInitTableException(Class tableElem) { - super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null")); - } - - /* package */ > ORMInitTableException(Class tableElem, Throwable t) { - super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"), t); - } - -} diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLWhere.java b/Core/src/main/java/fr/pandacube/util/orm/SQLWhere.java deleted file mode 100644 index 53ff67c..0000000 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLWhere.java +++ /dev/null @@ -1,43 +0,0 @@ -package fr.pandacube.util.orm; - -import java.util.List; - -import org.javatuples.Pair; - -import fr.pandacube.util.Log; - -public abstract class SQLWhere> { - - public abstract Pair> toSQL() throws ORMException; - - @Override - public String toString() { - try { - return toSQL().getValue0(); - } catch (ORMException e) { - Log.warning(e); - return "[SQLWhere.toString() error (see logs)]"; - } - } - - public SQLWhereAnd and(SQLWhere other) { - return new SQLWhereAnd().and(this).and(other); - } - - public SQLWhereOr or(SQLWhere other) { - return new SQLWhereOr().or(this).or(other); - } - - public static > SQLWhereAnd and() { - return new SQLWhereAnd<>(); - } - - public static > SQLWhereOr or() { - return new SQLWhereOr<>(); - } - - public static String escapeLike(String str) { - return str.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%"); - } - -} diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereAnd.java b/Core/src/main/java/fr/pandacube/util/orm/SQLWhereAnd.java deleted file mode 100644 index b30966a..0000000 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereAnd.java +++ /dev/null @@ -1,15 +0,0 @@ -package fr.pandacube.util.orm; - -public class SQLWhereAnd> extends SQLWhereChain { - - /* package */ SQLWhereAnd() { - super(SQLBoolOp.AND); - } - - @Override - public SQLWhereAnd and(SQLWhere other) { - add(other); - return this; - } - -} diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereChain.java b/Core/src/main/java/fr/pandacube/util/orm/SQLWhereChain.java deleted file mode 100644 index 45b649f..0000000 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereChain.java +++ /dev/null @@ -1,58 +0,0 @@ -package fr.pandacube.util.orm; - -import java.util.ArrayList; -import java.util.List; - -import org.javatuples.Pair; - -public abstract class SQLWhereChain> extends SQLWhere { - - private SQLBoolOp operator; - protected List> conditions = new ArrayList<>(); - - /* package */ SQLWhereChain(SQLBoolOp op) { - if (op == null) throw new IllegalArgumentException("op can't be null"); - operator = op; - } - - protected void add(SQLWhere sqlWhere) { - if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null"); - conditions.add(sqlWhere); - } - - @Override - public Pair> toSQL() throws ORMException { - if (conditions.isEmpty()) { - throw new ORMException("SQLWhereChain needs at least one element inside !"); - } - - String sql = ""; - List params = new ArrayList<>(); - boolean first = true; - - for (SQLWhere w : conditions) { - if (!first) sql += " " + operator.sql + " "; - first = false; - - Pair> ret = w.toSQL(); - sql += "(" + ret.getValue0() + ")"; - params.addAll(ret.getValue1()); - } - - return new Pair<>(sql, params); - } - - /* package */ enum SQLBoolOp { - /** Equivalent to SQL "AND" */ - AND("AND"), - /** Equivalent to SQL "OR" */ - OR("OR"); - /* package */ final String sql; - - private SQLBoolOp(String s) { - sql = s; - } - - } - -} diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereComp.java b/Core/src/main/java/fr/pandacube/util/orm/SQLWhereComp.java deleted file mode 100644 index 7f3b739..0000000 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereComp.java +++ /dev/null @@ -1,58 +0,0 @@ -package fr.pandacube.util.orm; - -import java.util.ArrayList; -import java.util.List; - -import org.javatuples.Pair; - -/* package */ class SQLWhereComp> extends SQLWhere { - - private SQLField left; - private SQLComparator comp; - private Object right; - - /** - * Compare a field with a value - * - * @param l the field at left of the comparison operator. Can't be null - * @param c the comparison operator, can't be null - * @param r the value at right of the comparison operator. Can't be null - */ - /* package */ SQLWhereComp(SQLField l, SQLComparator c, T r) { - if (l == null || r == null || c == null) - throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null"); - left = l; - comp = c; - right = r; - } - - @Override - public Pair> toSQL() throws ORMException { - List params = new ArrayList<>(); - SQLElement.addValueToSQLObjectList(params, left, right); - return new Pair<>("`" + left.getName() + "` " + comp.sql + " ? ", params); - } - - /* package */ enum SQLComparator { - /** Equivalent to SQL "=" */ - EQ("="), - /** Equivalent to SQL ">" */ - GT(">"), - /** Equivalent to SQL ">=" */ - GEQ(">="), - /** Equivalent to SQL "<" */ - LT("<"), - /** Equivalent to SQL "<=" */ - LEQ("<="), - /** Equivalent to SQL "!=" */ - NEQ("!="); - - /* package */ final String sql; - - private SQLComparator(String s) { - sql = s; - } - - } - -} diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereIn.java b/Core/src/main/java/fr/pandacube/util/orm/SQLWhereIn.java deleted file mode 100644 index bb1197b..0000000 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereIn.java +++ /dev/null @@ -1,38 +0,0 @@ -package fr.pandacube.util.orm; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.javatuples.Pair; - -/* package */ class SQLWhereIn> extends SQLWhere { - - private SQLField field; - private Collection values; - - /* package */ SQLWhereIn(SQLField f, Collection v) { - if (f == null || v == null) - throw new IllegalArgumentException("All arguments for SQLWhereIn constructor can't be null"); - field = f; - values = v; - } - - @Override - public Pair> toSQL() throws ORMException { - List params = new ArrayList<>(); - - if (values.isEmpty()) - return new Pair<>(" 1=0 ", params); - - for (Object v : values) - SQLElement.addValueToSQLObjectList(params, field, v); - - char[] questions = new char[values.size() == 0 ? 0 : (values.size() * 2 - 1)]; - for (int i = 0; i < questions.length; i++) - questions[i] = i % 2 == 0 ? '?' : ','; - - return new Pair<>("`" + field.getName() + "` IN (" + new String(questions) + ") ", params); - } - -} diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereLike.java b/Core/src/main/java/fr/pandacube/util/orm/SQLWhereLike.java deleted file mode 100644 index b0950fb..0000000 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereLike.java +++ /dev/null @@ -1,33 +0,0 @@ -package fr.pandacube.util.orm; - -import java.util.ArrayList; -import java.util.List; - -import org.javatuples.Pair; - -/* package */ class SQLWhereLike> extends SQLWhere { - - private SQLField field; - private String likeExpr; - - /** - * Compare a field with a value - * - * @param f the field at left of the LIKE keyword. Can't be null - * @param like the like expression. - */ - /* package */ SQLWhereLike(SQLField f, String like) { - if (f == null || like == null) - throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null"); - field = f; - likeExpr = like; - } - - @Override - public Pair> toSQL() { - ArrayList params = new ArrayList<>(); - params.add(likeExpr); - return new Pair<>("`" + field.getName() + "` LIKE ? ", params); - } - -} diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereNull.java b/Core/src/main/java/fr/pandacube/util/orm/SQLWhereNull.java deleted file mode 100644 index 33089b5..0000000 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereNull.java +++ /dev/null @@ -1,37 +0,0 @@ -package fr.pandacube.util.orm; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; - -import org.javatuples.Pair; - -import fr.pandacube.util.Log; - -/* package */ class SQLWhereNull> extends SQLWhere { - - private SQLField fild; - private boolean nulll; - - /** - * Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition. - * - * @param field the field to check null / not null state - * @param isNull true if we want to ckeck if "IS NULL", or false to check if - * "IS NOT NULL" - */ - /* package */ SQLWhereNull(SQLField field, boolean isNull) { - if (field == null) throw new IllegalArgumentException("field can't be null"); - if (!field.canBeNull) Log.getLogger().log(Level.WARNING, - "Useless : Trying to check IS [NOT] NULL on the field " + field.getSQLElementType().getName() + "#" - + field.getName() + " which is declared in the ORM as 'can't be null'"); - fild = field; - nulll = isNull; - } - - @Override - public Pair> toSQL() { - return new Pair<>("`" + fild.getName() + "` IS " + ((nulll) ? "NULL" : "NOT NULL"), new ArrayList<>()); - } - -} diff --git a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereOr.java b/Core/src/main/java/fr/pandacube/util/orm/SQLWhereOr.java deleted file mode 100644 index 6cb4ab4..0000000 --- a/Core/src/main/java/fr/pandacube/util/orm/SQLWhereOr.java +++ /dev/null @@ -1,15 +0,0 @@ -package fr.pandacube.util.orm; - -public class SQLWhereOr> extends SQLWhereChain { - - /* package */ SQLWhereOr() { - super(SQLBoolOp.OR); - } - - @Override - public SQLWhereOr or(SQLWhere other) { - add(other); - return this; - } - -} diff --git a/Paper/.classpath b/Paper/.classpath new file mode 100644 index 0000000..0e36958 --- /dev/null +++ b/Paper/.classpath @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Paper/.gitignore b/Paper/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/Paper/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/Paper/.project b/Paper/.project new file mode 100644 index 0000000..7d381e0 --- /dev/null +++ b/Paper/.project @@ -0,0 +1,23 @@ + + + pandalib-paper + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/Paper/.settings/org.eclipse.core.resources.prefs b/Paper/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..e9441bb --- /dev/null +++ b/Paper/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding/=UTF-8 diff --git a/Paper/.settings/org.eclipse.jdt.core.prefs b/Paper/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..2af1e7b --- /dev/null +++ b/Paper/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=11 diff --git a/Paper/.settings/org.eclipse.m2e.core.prefs b/Paper/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/Paper/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/Paper/pom.xml b/Paper/pom.xml new file mode 100644 index 0000000..294adb9 --- /dev/null +++ b/Paper/pom.xml @@ -0,0 +1,38 @@ + + 4.0.0 + + + fr.pandacube.lib + pandalib-parent + 1.0-SNAPSHOT + + + pandalib-paper + + PandaLib-Paper + + + + papermc + https://papermc.io/repo/repository/maven-public/ + + + + + + fr.pandacube.lib + pandalib-core + ${project.version} + compile + + + + + com.destroystokyo.paper + paper-api + ${paper.version}-SNAPSHOT + compile + + + + \ No newline at end of file diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/PandaLibPaper.java b/Paper/src/main/java/fr/pandacube/lib/paper/PandaLibPaper.java new file mode 100644 index 0000000..558b222 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/PandaLibPaper.java @@ -0,0 +1,17 @@ +package fr.pandacube.lib.paper; + +import org.bukkit.plugin.Plugin; + +public class PandaLibPaper { + + private static Plugin plugin; + + public static void init(Plugin plugin) { + PandaLibPaper.plugin = plugin; + } + + public static Plugin getPlugin() { + return plugin; + } + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/gui/GUIHotBar.java b/Paper/src/main/java/fr/pandacube/lib/paper/gui/GUIHotBar.java new file mode 100644 index 0000000..390fc51 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/gui/GUIHotBar.java @@ -0,0 +1,224 @@ +package fr.pandacube.lib.paper.gui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +import fr.pandacube.lib.core.util.Log; +import fr.pandacube.lib.paper.util.BukkitEvent; + +/** + * Managed a « lobby » type hotbar menu/inventory. It represents items in the player inventory on which you can right click on it. + * The player can't move or drop these items. + * + */ +public class GUIHotBar implements Listener { + + private Map> itemsAndSetters = new HashMap<>(); + + private Map> itemsAndRunnables = new HashMap<>(); + + private final int defltSlot; + + private List currentPlayers = new ArrayList<>(); + + public GUIHotBar(int defaultSlot) { + defltSlot = Math.max(0, Math.min(8, defaultSlot)); + + BukkitEvent.register(this); + } + + /** + * Add the item to this hotbar menu. if there is already players hooked to this hotbar, the item will be directly added to + * their inventories. + * @param i the item stack + * @param setter code executed to put the item in the inventory. Additionally check for permission before doing the addition. + * @param run the Runnable to run when the user right click on the item in the hotbar. + * @return + */ + public GUIHotBar addItem(ItemStack i, BiConsumer setter, Consumer run) { + itemsAndSetters.put(i, setter); + itemsAndRunnables.put(i, run); + + for (Player p : currentPlayers) + addItemToPlayer(p, i); + + return this; + } + + /** + * Add the hotbar elements to this player. + * + * The players is automatically removed when it quit. You can remove it before by calling {@link #removePlayer(Player)}. + * @param p + */ + public void addPlayer(Player p) { + if (!currentPlayers.contains(p)) + currentPlayers.add(p); + + for (ItemStack is : itemsAndSetters.keySet()) { + addItemToPlayer(p, is); + } + + p.getInventory().setHeldItemSlot(defltSlot); + } + + /** + * Detach this player from this hotbar manager and removes the managed items from the players inventory. + * @param p + */ + public void removePlayer(Player p) { + if (!currentPlayers.contains(p)) + return; + + for (ItemStack is : itemsAndSetters.keySet()) { + removeItemFromPlayer(p, is); + } + + currentPlayers.remove(p); + + } + + + public boolean containsPlayer(Player p) { + return currentPlayers.contains(p); + } + + + + public void removeAllPlayers() { + for (Player p : new ArrayList<>(currentPlayers)) + removePlayer(p); + } + + + + + + public void addItemToPlayer(Player p, ItemStack is) { + if (!itemsAndSetters.containsKey(is)) + throw new IllegalArgumentException("The provided ItemStack is not registered in this HotbarMenu"); + if (!currentPlayers.contains(p)) + throw new IllegalArgumentException("The provided Player is not registered in this HotbarMenu"); + itemsAndSetters.get(is).accept(p.getInventory(), is.clone()); + } + + public void removeItemFromPlayer(Player p, ItemStack is) { + p.getInventory().remove(is); + } + + + + + + @EventHandler + public void onPlayerDropItem(PlayerDropItemEvent event) { + if (!currentPlayers.contains(event.getPlayer())) + return; + + ItemStack item = event.getItemDrop().getItemStack(); + for (ItemStack managed : itemsAndSetters.keySet()) { + if (item != null && item.isSimilar(managed)) { + event.setCancelled(true); + return; + } + } + } + + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (!currentPlayers.contains(event.getPlayer())) + return; + + if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_AIR) + return; + + ItemStack item = event.getItem(); + if (item == null) + return; + + Player p = event.getPlayer(); + + for (ItemStack is : itemsAndRunnables.keySet()) { + if (item.isSimilar(is)) { + try { + itemsAndRunnables.get(is).accept(p); + } catch (Exception e) { + Log.severe(e); + } + event.setCancelled(true); + return; + } + } + } + + + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (event.getClickedInventory() == null || !(event.getClickedInventory() instanceof PlayerInventory)) + return; + + PlayerInventory inv = (PlayerInventory) event.getClickedInventory(); + + if (!currentPlayers.contains(inv.getHolder())) + return; + + ItemStack item = event.getCurrentItem(); + + for (ItemStack is : itemsAndSetters.keySet()) { + if (item != null && item.isSimilar(is)) { + try { + itemsAndRunnables.get(is).accept((Player) inv.getHolder()); + } catch (Exception e) { + Log.severe(e); + } + event.setCancelled(true); + return; + } + } + } + + + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + removePlayer(event.getPlayer()); + } + + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + if (!currentPlayers.contains(event.getEntity())) + return; + event.getDrops().removeAll(itemsAndSetters.keySet()); + } + + @EventHandler + public void onPlayerRespawn(PlayerRespawnEvent event) { + if (!currentPlayers.contains(event.getPlayer())) + return; + + currentPlayers.remove(event.getPlayer()); + addPlayer(event.getPlayer()); + } + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/gui/GUIInventory.java b/Paper/src/main/java/fr/pandacube/lib/paper/gui/GUIInventory.java new file mode 100644 index 0000000..054bc21 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/gui/GUIInventory.java @@ -0,0 +1,251 @@ +package fr.pandacube.lib.paper.gui; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; + +import com.google.common.collect.ImmutableMap; + +import fr.pandacube.lib.core.chat.Chat; +import fr.pandacube.lib.core.util.Callback; +import fr.pandacube.lib.paper.util.ItemStackBuilder; + +public class GUIInventory implements Listener { + + public static final Map FAKE_ENCHANT = ImmutableMap.of(Enchantment.DURABILITY, 1); + + private Player player; + private Inventory inv; + private Callback onCloseEvent; + private boolean isOpened = false; + private Map> onClickEvents; + + public GUIInventory(Player p, int nbLines, String title, Callback closeEventAction, + Plugin pl) { + inv = Bukkit.createInventory(null, nbLines * 9, title); + + setCloseEvent(closeEventAction); + + onClickEvents = new HashMap<>(); + + player = p; + + Bukkit.getPluginManager().registerEvents(this, pl); + + } + + protected void setCloseEvent(Callback closeEventAction) { + onCloseEvent = closeEventAction; + } + + /** + * @param clickEventActions (l'event passé en paramètre de la méthode done a + * été pré-annulée. Pour la rétablir, faites un + * event.setCancelled(false)). + */ + public void setButtonIfEmpty(int p, ItemStack iStack, Callback clickEventActions) { + if (inv.getItem(p) == null) + setButton(p, iStack, clickEventActions); + } + + /** + * @param clickEventActions (l'event passé en paramètre de la méthode done a + * été pré-annulée. Pour la rétablir, faites un + * event.setCancelled(false)). + */ + public void setButton(int p, ItemStack iStack, Callback clickEventActions) { + inv.setItem(p, iStack); + changeClickEventAction(p, clickEventActions); + } + + /** + * @param clickEventActions (l'event passé en paramètre de la méthode done a + * été pré-annulée. Pour la rétablir, faites un + * event.setCancelled(false)). + */ + public void changeClickEventAction(int p, Callback clickEventActions) { + onClickEvents.put(p, clickEventActions); + } + + public ItemStack getItemStack(int p) { + return inv.getItem(p); + } + + public void open() { + if (isOpened) return; + player.openInventory(inv); + isOpened = true; + } + + public void forceClose() { + if (!isOpened) return; + player.closeInventory(); // internally calls the InventoryCloseEvent + } + + public boolean isOpen() { + return isOpened; + } + + public void clear() { + onClickEvents.clear(); + inv.clear(); + } + + public void clear(int firstElement, int nbElement) { + for (int i = firstElement; i < firstElement + nbElement; i++) { + inv.setItem(i, null); + onClickEvents.remove(i); + } + } + + public Inventory getInventory() { + return inv; + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (!event.getWhoClicked().equals(player)) return; + if (!isOpened) return; + if (!event.getView().getTopInventory().equals(inv)) return; + + event.setCancelled(true); + + // on ne réagit pas aux clics hors de l'inventaire du dessus. + if (event.getClickedInventory() != event.getView().getTopInventory()) return; + + Callback callback = onClickEvents.get(event.getSlot()); + if (callback != null) callback.done(event); + + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + if (!event.getPlayer().equals(player)) return; + if (!isOpened) return; + if (!event.getView().getTopInventory().equals(inv)) return; + + HandlerList.unregisterAll(this); + + if (onCloseEvent != null) + onCloseEvent.done(event); + isOpened = false; + } + + + + + + + + + + + public static ItemStack buildButton(ItemStack base, Integer amount, Chat displayName, + List lores, Map enchantments) { + + ItemStackBuilder iStackBuilder = ItemStackBuilder.of(base); + + if (amount != null) + iStackBuilder.amount(amount); + if (displayName != null) + iStackBuilder.displayName(displayName); + if (lores != null) + iStackBuilder.lore(lores); + if (enchantments != null) { + if (enchantments == FAKE_ENCHANT) + iStackBuilder.fakeEnchant(); + else { + for (Entry e : enchantments.entrySet()) { + iStackBuilder.enchant(e.getKey(), e.getValue()); + } + } + } + + return iStackBuilder.build(); + } + public static ItemStack buildButton(ItemStack base, Chat displayName, List lores, Map enchantments) { + return buildButton(base, null, displayName, lores, enchantments); + } + public static ItemStack buildButton(ItemStack base, Integer amount, Chat displayName, Map enchantments) { + return buildButton(base, amount, displayName, null, enchantments); + } + public static ItemStack buildButton(ItemStack base, Chat displayName, Map enchantments) { + return buildButton(base, null, displayName, null, enchantments); + } + public static ItemStack buildButton(ItemStack base, Integer amount, Chat displayName, List lores) { + return buildButton(base, amount, displayName, lores, null); + } + public static ItemStack buildButton(ItemStack base, Chat displayName, List lores) { + return buildButton(base, null, displayName, lores, null); + } + public static ItemStack buildButton(ItemStack base, Integer amount, Chat displayName) { + return buildButton(base, amount, displayName, null, null); + } + public static ItemStack buildButton(ItemStack base, Chat displayName) { + return buildButton(base, null, displayName, null, null); + } + public static ItemStack buildButton(ItemStack base, Integer amount) { + return buildButton(base, amount, null, null, null); + } + + + + + public static ItemStack buildButton(Material m, int amount, Chat displayName, List lores, Map enchantments) { + return buildButton(new ItemStack(m, amount), displayName, lores, enchantments); + } + public static ItemStack buildButton(Material m, Chat displayName, List lores, Map enchantments) { + return buildButton(m, 1, displayName, lores, enchantments); + } + public static ItemStack buildButton(Material m, int amount, Chat displayName, Map enchantments) { + return buildButton(m, amount, displayName, null, enchantments); + } + public static ItemStack buildButton(Material m, Chat displayName, Map enchantments) { + return buildButton(m, 1, displayName, null, enchantments); + } + public static ItemStack buildButton(Material m, int amount, Chat displayName, List lores) { + return buildButton(m, amount, displayName, lores, null); + } + public static ItemStack buildButton(Material m, Chat displayName, List lores) { + return buildButton(m, 1, displayName, lores, null); + } + public static ItemStack buildButton(Material m, int amount, Chat displayName) { + return buildButton(m, amount, displayName, null, null); + } + public static ItemStack buildButton(Material m, Chat displayName) { + return buildButton(m, 1, displayName, null, null); + } + public static ItemStack buildButton(Material m, int amount) { + return buildButton(m, amount, null, null, null); + } + + + + + + + + + + + public static int nbLineForNbElements(int nb) { + return nb / 9 + ((nb % 9 == 0) ? 0 : 1); + } + + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java new file mode 100644 index 0000000..97a86c6 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java @@ -0,0 +1,100 @@ +package fr.pandacube.lib.paper.util; + +import java.util.Iterator; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.util.BlockVector; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; + +import fr.pandacube.lib.core.util.RandomUtil; + +/** + * Checkpoint represented as a 3D Axis and Block Aligned Bounding Box (sort of AABB). + * Represent the littelest cuboid selection of blocks that contains the bounding box + * passed to the constructor. + */ +public class AABBBlock implements Iterable { + + public final Vector pos1, pos2; + + public AABBBlock(Vector p1, Vector p2) { + this(p1.getBlockX(), p1.getBlockY(), p1.getBlockZ(), p2.getBlockX(), p2.getBlockY(), p2.getBlockZ()); + } + + public AABBBlock(Location l1, Location l2) { + this(l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), l2.getBlockX(), l2.getBlockY(), l2.getBlockZ()); + } + + public AABBBlock(int p1x, int p1y, int p1z, int p2x, int p2y, int p2z) { + /* + * Prends les points extérieurs permettant de former un bouding box englobant + * celui représenté par v1 et v2, et étant aligné au quadrillage des blocs. + */ + pos1 = new Vector(Math.min(p1x, p2x), + Math.min(p1y, p2y), + Math.min(p1z, p2z)); + pos2 = new Vector(Math.max(p1x, p2x) + 1, + Math.max(p1y, p2y) + 1, + Math.max(p1z, p2z) + 1); + } + + + public boolean isInside(Vector v) { + return v.isInAABB(pos1, pos2); + } + public boolean isInside(Location l) { + return isInside(l.toVector()); + } + public boolean isInside(Entity p) { + return isInside(p.getLocation()); + } + + public Vector getCenter() { + return pos1.clone().add(pos2).multiply(0.5); + } + + public BoundingBox asBukkitBoundingBox() { + return new BoundingBox(pos1.getX(), pos1.getY(), pos1.getZ(), + pos2.getX(), pos2.getY(), pos2.getZ()); + } + + public Vector getRandomPosition() { + double x = RandomUtil.nextDoubleBetween(pos1.getX(), pos2.getX()); + double y = RandomUtil.nextDoubleBetween(pos1.getY(), pos2.getY()); + double z = RandomUtil.nextDoubleBetween(pos1.getZ(), pos2.getZ()); + return new Vector(x, y, z); + } + + @Override + public Iterator iterator() { + return new Iterator() { + private int x = pos1.getBlockX(), + y = pos1.getBlockY(), + z = pos1.getBlockZ(); + + @Override + public boolean hasNext() { + return x < pos2.getBlockX(); + } + @Override + public BlockVector next() { + BlockVector bv = new BlockVector(x, y, z); + + z++; + if (z >= pos2.getBlockZ()) { + y++; + z = pos1.getBlockZ(); + if (y >= pos2.getBlockY()) { + x++; + y = pos1.getBlockY(); + } + } + + return bv; + } + }; + } + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/AutoUpdatedBossBar.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/AutoUpdatedBossBar.java new file mode 100644 index 0000000..b2a055e --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/AutoUpdatedBossBar.java @@ -0,0 +1,199 @@ +package fr.pandacube.lib.paper.util; + +import java.util.Objects; +import java.util.Timer; +import java.util.TimerTask; +import java.util.function.Predicate; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BossBar; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; + +import fr.pandacube.lib.core.util.Log; +import fr.pandacube.lib.paper.PandaLibPaper; + +public class AutoUpdatedBossBar implements Listener { + + public final BossBar bar; + public final BarUpdater updater; + + private Timer timer = null; + private BukkitTask bukkitTask = null; + + private boolean scheduled = false; + + private boolean followPlayerList = false; + private Predicate playerCondition = null; + + public AutoUpdatedBossBar(BossBar bar, BarUpdater updater) { + this.bar = bar; + this.updater = updater; + } + + + /** + * Schedule the update of this bossbar with synchronisation with the system clock. + * The underlying method called is {@link Timer#schedule(TimerTask, long, long)}. + * The updater is executed in a separate Thread. + * @param msDelay ms before running the first update of this bossbar + * @param msPeriod ms between each call of the updater + */ + public synchronized void scheduleUpdateTimeSyncThreadAsync(long msDelay, long msPeriod) { + if (scheduled) + throw new IllegalStateException("Can't schedule an already scheduled bossbar update"); + + scheduled = true; + timer = new Timer("Panda BossBarUpdater - " + ChatColor.stripColor(bar.getTitle())); + timer.schedule(new TimerTask() { + @Override + public void run() { + try { + updater.update(AutoUpdatedBossBar.this); + } catch(Throwable e) { + Log.severe("Error while updating an AutoUpdatedBossBar", e); + } + } + }, msDelay, msPeriod); + } + + /** + * Schedule the update of this bossbar with synchronisation with the main Thread of the + * current Minecraft server (follow the tick count progress). + * The underlying method called is {@link BukkitScheduler#runTaskTimer(org.bukkit.plugin.Plugin, Runnable, long, long)}. + * The updater is executed by the Server Thread. + * @param tickDelay number of server tick before running the first update of this bossbar + * @param tickPeriod number of server tick between each call of the updater + */ + public synchronized void scheduleUpdateTickSyncThreadSync(long tickDelay, long tickPeriod) { + if (scheduled) + throw new IllegalStateException("Can't schedule an already scheduled bossbar update"); + + scheduled = true; + bukkitTask = Bukkit.getServer().getScheduler() + .runTaskTimer(PandaLibPaper.getPlugin(), () -> { + synchronized (bar) { + try { + updater.update(this); + } catch(Throwable e) { + Log.severe("Error while updating an AutoUpdatedBossBar", e); + } + } + }, tickDelay, tickPeriod); + + } + + + + public synchronized void followLoginLogout(Predicate condition) { + playerCondition = condition; + if (followPlayerList == true) + return; + followPlayerList = true; + BukkitEvent.register(this); + Bukkit.getServer().getOnlinePlayers().forEach(p -> { + onPlayerJoin(new PlayerJoinEvent(p, "")); + }); + } + + public synchronized void unfollowPlayerList() { + if (followPlayerList == false) + return; + followPlayerList = false; + playerCondition = null; + PlayerJoinEvent.getHandlerList().unregister(this); + PlayerQuitEvent.getHandlerList().unregister(this); + } + + @EventHandler(priority=EventPriority.MONITOR) + public synchronized void onPlayerJoin(PlayerJoinEvent event) { + if (followPlayerList == false) + return; + if (playerCondition != null && !playerCondition.test(event.getPlayer())) + return; + synchronized (bar) { + bar.addPlayer(event.getPlayer()); + } + } + + @EventHandler(priority=EventPriority.HIGH) + public synchronized void onPlayerQuit(PlayerQuitEvent event) { + if (followPlayerList == false) + return; + synchronized (bar) { + bar.removePlayer(event.getPlayer()); + } + } + + + public synchronized void cancel() { + if (!scheduled) + throw new IllegalStateException("Can't cancel a not scheduled bossbar update"); + scheduled = false; + if (timer != null) { + timer.cancel(); + timer = null; + } + if (bukkitTask != null) { + bukkitTask.cancel(); + bukkitTask = null; + } + + } + + + + @FunctionalInterface + public interface BarUpdater { + public void update(AutoUpdatedBossBar bar); + } + + + + /** + * Utility method to update the title of the bossbar without unnecessary packet. + * @param title + */ + public void setTitle(String title) { + synchronized (bar) { + if (!Objects.equals(title, bar.getTitle())) + bar.setTitle(title); + } + } + + /** + * Utility method to update the color of the bossbar without unnecessary packet. + * @param title + */ + public void setColor(BarColor color) { + synchronized (bar) { + if (color != bar.getColor()) + bar.setColor(color); + } + } + + /** + * Utility method to update the progress of the bossbar without unnecessary packet. + * @param title + */ + public void setProgress(double progress) { + synchronized (bar) { + if (progress != bar.getProgress()) + bar.setProgress(progress); + } + } + + + + + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/BukkitChatColorUtil.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/BukkitChatColorUtil.java new file mode 100644 index 0000000..faa4b85 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/BukkitChatColorUtil.java @@ -0,0 +1,70 @@ +package fr.pandacube.lib.paper.util; + +import org.bukkit.DyeColor; + +import net.md_5.bungee.api.ChatColor; + +public class BukkitChatColorUtil { + + + + + /** + * Returns the {@link ChatColor} that is visually the closest from the provided {@link DyeColor} when used on a sign. + * + * Multiple {@link DyeColor} may return the same + * @param dye the provided dye color + * @return the closest chat color from {@code dye} + */ + public static ChatColor fromDyeToSignColor(DyeColor dye) { + //org.bukkit.Color col = dye.getColor(); + //return ChatColor.of(new Color(col.asRGB())); + // hmmm this is not that simple, of course + + // black + switch (dye) { + case BLACK: + return ChatColor.of("#000000"); + case RED: + return ChatColor.of("#650000"); + case GREEN: + return ChatColor.of("#006500"); + case BROWN: + return ChatColor.of("#361B07"); + case BLUE: + return ChatColor.of("#000065"); + case PURPLE: + return ChatColor.of("#3F0C5F"); + case CYAN: + return ChatColor.of("#006565"); + case LIGHT_GRAY: + return ChatColor.of("#535353"); + case GRAY: + return ChatColor.of("#323232"); + case PINK: + return ChatColor.of("#652947"); + case LIME: + return ChatColor.of("#4B6500"); + case YELLOW: + return ChatColor.of("#656500"); + case LIGHT_BLUE: + return ChatColor.of("#3C4B51"); + case MAGENTA: + return ChatColor.of("#650065"); + case ORANGE: + return ChatColor.of("#65280C"); + case WHITE: + return ChatColor.of("#656565"); + } + throw new IllegalArgumentException("Unknown DyeColor: " + dye); + } + + + + public static org.bukkit.ChatColor fromBungeeToBukkit(ChatColor color) { + return org.bukkit.ChatColor.valueOf(color.getName().toUpperCase()); + } + + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/BukkitEvent.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/BukkitEvent.java new file mode 100644 index 0000000..e0dec4a --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/BukkitEvent.java @@ -0,0 +1,97 @@ +package fr.pandacube.lib.paper.util; + +import java.lang.reflect.Method; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.bukkit.plugin.IllegalPluginAccessException; + +import fr.pandacube.lib.core.util.ReflexionUtil; +import fr.pandacube.lib.paper.PandaLibPaper; + +public class BukkitEvent { + + public static Listener register(Class eventClass, EventListener eventExecutor) { + return register(eventClass, eventExecutor, EventPriority.NORMAL, false); + } + + public static Listener register(Class eventClass, EventListener eventExecutor, EventPriority priority) { + return register(eventClass, eventExecutor, priority, false); + } + + public static Listener register(Class eventClass, EventListener eventExecutor, boolean ignoreCancelled) { + return register(eventClass, eventExecutor, EventPriority.NORMAL, ignoreCancelled); + } + + public static Listener register(Class eventClass, EventListener eventExecutor, EventPriority priority, boolean ignoreCancelled) { + Bukkit.getPluginManager().registerEvent(eventClass, eventExecutor, priority, eventExecutor, PandaLibPaper.getPlugin(), ignoreCancelled); + return eventExecutor; + } + + public static void register(Listener l) { + Bukkit.getPluginManager().registerEvents(l, PandaLibPaper.getPlugin()); + } + + + + public static void unregister(Listener listener) { + HandlerList.unregisterAll(listener); + } + + + + public static List> getAllEventClasses() { + List> classes = ReflexionUtil.getAllSubclasses(Event.class); + classes.removeIf(e -> getHandlerList(e) == null); + return classes; + } + + + + // method retrieved from OB.plugin.SimplePluginManager#getEventListeners + public static HandlerList getHandlerList(Class type) throws IllegalPluginAccessException { + try { + Method method = getRegistrationClass(type).getDeclaredMethod("getHandlerList", new Class[0]); + method.setAccessible(true); + return (HandlerList)method.invoke(null, new Object[0]); + } + catch (Exception e) { + return null; + } + } + + // method retrieved from OB.plugin.SimplePluginManager + private static Class getRegistrationClass(Class clazz) throws IllegalPluginAccessException { + try { + clazz.getDeclaredMethod("getHandlerList", new Class[0]); + return clazz; + } + catch (NoSuchMethodException e) { + if (clazz.getSuperclass() != null && !clazz.getSuperclass().equals(Event.class) && Event.class.isAssignableFrom(clazz.getSuperclass())) { + return getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class)); + } + return null; + } + } + + + + public interface EventListener extends Listener, EventExecutor { + + public abstract void onEvent(E event); + + @SuppressWarnings("unchecked") + @Override + default void execute(Listener var1, Event var2) throws EventException { + onEvent((E)var2); + } + } + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/ColorUtil.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/ColorUtil.java new file mode 100644 index 0000000..2dc8bac --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/ColorUtil.java @@ -0,0 +1,50 @@ +package fr.pandacube.lib.paper.util; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Color; + +public class ColorUtil { + + /* + * Rainbow + */ + private static List rainbowColors = new ArrayList<>(); + + /** + * Get a rainbow color + * @param variation from 0 (include) to 1 (exclude). + * 0 is red, 1/6 is yellow, 2/6 is green, 3/6 is cyan, 4/6 is blue, 5/6 is magenta + * @return + */ + public static Color getRainbowColor(double variation) { + synchronized (rainbowColors) { + if (rainbowColors.isEmpty()) { + for (int g=0; g<255; g++) rainbowColors.add(Color.fromRGB(255, g, 0)); + for (int r=255; r>0; r--) rainbowColors.add(Color.fromRGB(r, 255, 0)); + for (int b=0; b<255; b++) rainbowColors.add(Color.fromRGB(0, 255, b)); + for (int g=255; g>0; g--) rainbowColors.add(Color.fromRGB(0, g, 255)); + for (int r=0; r<255; r++) rainbowColors.add(Color.fromRGB(r, 0, 255)); + for (int b=255; b>0; b--) rainbowColors.add(Color.fromRGB(255, 0, b)); + } + } + + while (variation >= 1) variation -= 1d; + while (variation < 0) variation += 1d; + + return rainbowColors.get((int)(variation * rainbowColors.size())); + } + + + + + + + + + + + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/EntityStackUtil.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/EntityStackUtil.java new file mode 100644 index 0000000..a45f7a8 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/EntityStackUtil.java @@ -0,0 +1,52 @@ +package fr.pandacube.lib.paper.util; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; + +/** + * Permet de gérer les entités qui se transportent les uns les autres + * + * Ce groupement d'entité est appelé une "pile d'entité". + * + * Par exemple, un cheval et son monteur représente à eux deux une pile + * d'entité, dont + * l'élement tout en bas est le cheval + */ +public class EntityStackUtil { + + /** + * Déplace une pile d'entité vers l'endroit défini + * + * @param e Une entité faisant parti de la pile d'entité à téléporter + * @param l La position vers lequel envoyer toute la pile + */ + public static void teleportStack(Entity e, Location l) { + + // on se place sur l'entité tout en bas de la pile + Entity entTemp = e; + while (entTemp.getVehicle() != null) + entTemp = entTemp.getVehicle(); + + /* la possibilité d'avoir plusieurs passagers sur un entité rend le code + * commenté qui suit invalide. On le remplace temporairement (voire + * définitivement si ça suffit) par le code encore en dessous + List stack = new ArrayList<>(); + do { + stack.add(entTemp); + entTemp = entTemp.getPassenger(); + } while (entTemp != null); + + if (stack.size() == 1) { + stack.get(0).teleport(l); + return; + } + + stack.get(0).eject(); + stack.get(0).teleport(l); + stack.get(0).setPassenger(stack.get(1)); + */ + + entTemp.teleport(l); // entTemp est l'entité le plus en bas de la "pile" d'entité + } + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/ExperienceUtil.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/ExperienceUtil.java new file mode 100644 index 0000000..17962da --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/ExperienceUtil.java @@ -0,0 +1,99 @@ +package fr.pandacube.lib.paper.util; + +import org.bukkit.entity.Player; + +/** + * A utility for managing Player experience properly.
+ * https://gist.github.com/Jikoo/30ec040443a4701b8980 + * + * @author Jikoo + */ +public class ExperienceUtil { + + /** + * Calculates a player's total exp based on level and progress to next. + * + * @see http://minecraft.gamepedia.com/Experience#Leveling_up + * + * @param player the Player + * + * @return the amount of exp the Player has + */ + public static int getExp(Player player) { + return getExpFromLevel(player.getLevel()) + Math.round(getExpToNext(player.getLevel()) * player.getExp()); + } + + /** + * Calculates total experience based on level. + * + * @see http://minecraft.gamepedia.com/Experience#Leveling_up + * + * "One can determine how much experience has been collected to reach a + * level using the equations: + * + * Total Experience = [Level]2 + 6[Level] (at levels 0-15) + * 2.5[Level]2 - 40.5[Level] + 360 (at levels 16-30) + * 4.5[Level]2 - 162.5[Level] + 2220 (at level 31+)" + * + * @param level the level + * + * @return the total experience calculated + */ + public static int getExpFromLevel(int level) { + if (level > 30) return (int) (4.5 * level * level - 162.5 * level + 2220); + if (level > 15) return (int) (2.5 * level * level - 40.5 * level + 360); + return level * level + 6 * level; + } + + /** + * Calculates level based on total experience. + * + * @param exp the total experience + * + * @return the level calculated + */ + public static double getLevelFromExp(int exp) { + if (exp > 1395) return (Math.sqrt(72 * exp - 54215) + 325) / 18; + if (exp > 315) return Math.sqrt(40 * exp - 7839) / 10 + 8.1; + if (exp > 0) return Math.sqrt(exp + 9) - 3; + return 0; + } + + /** + * @see http://minecraft.gamepedia.com/Experience#Leveling_up + * + * "The formulas for figuring out how many experience orbs you need to + * get to the next level are as follows: + * Experience Required = 2[Current Level] + 7 (at levels 0-15) + * 5[Current Level] - 38 (at levels 16-30) + * 9[Current Level] - 158 (at level 31+)" + */ + private static int getExpToNext(int level) { + if (level > 30) return 9 * level - 158; + if (level > 15) return 5 * level - 38; + return 2 * level + 7; + } + + /** + * Change a Player's exp. + *

+ * This method should be used in place of {@link Player#giveExp(int)}, which + * does not properly + * account for different levels requiring different amounts of experience. + * + * @param player the Player affected + * @param exp the amount of experience to add or remove + */ + public static void changeExp(Player player, int exp) { + exp += getExp(player); + + if (exp < 0) exp = 0; + + double levelAndExp = getLevelFromExp(exp); + + int level = (int) levelAndExp; + player.setLevel(level); + player.setExp((float) (levelAndExp - level)); + } + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/GameWorldUtils.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/GameWorldUtils.java new file mode 100644 index 0000000..8c84d9c --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/GameWorldUtils.java @@ -0,0 +1,97 @@ +package fr.pandacube.lib.paper.util; + +import java.io.File; +import java.io.IOException; +import java.util.function.Consumer; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.World.Environment; +import org.bukkit.WorldCreator; +import org.bukkit.event.Listener; + +import fr.pandacube.lib.core.util.BiMap; +import fr.pandacube.lib.core.util.FileUtils; +import fr.pandacube.lib.core.util.Log; +import fr.pandacube.lib.core.util.RandomUtil; + +public class GameWorldUtils implements Listener { + + private static BiMap gameWorld = new BiMap<>(); + + + public static World getOrLoadGameWorld(String world, Consumer operationOnLoad) throws IOException { + if (gameWorld.containsKey(world)) { + return gameWorld.get(world); + } + try { + return loadGameWorld(world, operationOnLoad); + } catch (IllegalStateException e) { + Log.severe(e); + return null; + } + } + + + + + public static World getGameWorldIfLoaded(String world) { + if (gameWorld.containsKey(world)) { + return gameWorld.get(world); + } + return null; + } + + + + + public static boolean unloadGameWorld(String world) { + if (gameWorld.containsKey(world)) { + World rem = gameWorld.remove(world); + String copiedName = rem.getName(); + boolean ret = Bukkit.unloadWorld(rem, false); + if (ret) + FileUtils.delete(new File(Bukkit.getWorldContainer(), copiedName)); + return ret; + } + return true; + } + + + + + public static boolean isGameWorldLoaded(String world) { + return gameWorld.containsKey(world); + } + + + + private static World loadGameWorld(String world, Consumer operationOnLoad) throws IOException { + if (gameWorld.containsKey(world)) + throw new IllegalStateException("GameWorld '"+world+"' is already loaded."); + + if (!new File(Bukkit.getWorldContainer(), world).isDirectory()) + throw new IllegalStateException("GameWorld '"+world+"' does not exist"); + + String copiedName = world + "_gen" + RandomUtil.nextIntBetween(100000, 999999); + + File srcDir = new File(Bukkit.getWorldContainer(), world); + File destDir = new File(Bukkit.getWorldContainer(), copiedName); + FileUtils.delete(destDir); + FileUtils.copy(srcDir, destDir); + new File(destDir, "session.lock").delete(); + new File(destDir, "uid.dat").delete(); + + World w = Bukkit.createWorld(new WorldCreator(copiedName).environment(Environment.NORMAL)); + w.setAutoSave(false); + gameWorld.put(world, w); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "mvm set hidden true "+copiedName); + operationOnLoad.accept(w); + return w; + } + + + + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/GeometryUtil.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/GeometryUtil.java new file mode 100644 index 0000000..bf50bd0 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/GeometryUtil.java @@ -0,0 +1,422 @@ +package fr.pandacube.lib.paper.util; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +public class GeometryUtil { + + /** + * Value equal to {@link Math#PI}. + */ + public static final double PI = Math.PI; + + /** + * Value equal to {@link Math#PI} / 2. + */ + public static final double PId2 = PI/2; + + /** + * Value equal to {@link Math#PI} * 2. + */ + public static final double PIx2 = PI*2; + + + + + + /* + * Player geometry + */ + + + /** + * The visual height of a Minecraft player skin, when he is standing up and not sneaking, + * from the ground where the player is standing on, to the above of the first layer of the head skin. + * It doesn't correspond to the player hitbox height.
+ *
+ * 1.88 is an approximated value, estimated by ingame tests. + */ + public static final double PLAYER_SKIN_HEIGHT = 1.88; + /** + * Value provided by Craftbukkit's {@code CraftPlayer#getEyeHeight(boolean)} source code + */ + public static final double PLAYER_EYE_HEIGHT = 1.62; + /** + * The visual height of a Minecraft player skin, when he is standing up and sneaking, + * from the ground where the player is standing on, to the above of the first layer of the head skin. + * It may not correspond to the player hitbox height.
+ *
+ * The current value is the height of the player's hitbox when sneaking. Even if this + * is close to the real value (tested in game), this is not the exact value. + */ + public static final double PLAYER_SKIN_HEIGHT_SNEAK = 1.65; + /** + * Value provided by Craftbukkit's {@code CraftPlayer#getEyeHeight(boolean)} source code + */ + public static final double PLAYER_EYE_HEIGHT_SNEAK = 1.54; + public static final double PLAYER_SKIN_PIXEL_SIZE = PLAYER_SKIN_HEIGHT / 32; + public static final double PLAYER_HEAD_ROTATION_HEIGHT = PLAYER_SKIN_PIXEL_SIZE * 24; + public static final double PLAYER_HEAD_ROTATION_HEIGHT_SNEAK = PLAYER_HEAD_ROTATION_HEIGHT - (PLAYER_SKIN_HEIGHT - PLAYER_SKIN_HEIGHT_SNEAK); + public static final double PLAYER_HEAD_SIZE = PLAYER_SKIN_PIXEL_SIZE * 8; + + + + + + + + + + + + + + + /** + * Get the {@link Location}s of the 8 vertex of the player head
+ * This method only work if the player is standing up + * (not dead, not glyding, not sleeping). + * @param playerLocation the location of the player, generally provided by {@link Player#getLocation()} + * @param isSneaking if the player is sneaking. Generally {@link Player#isSneaking()} + * @return an array of 8 {@link Location}s with x, y, and z values filled (yaw and pitch are ignored). + *

return[0] // top front left
+	 *return[1] // top front right
+	 *return[2] // bottom front left
+	 *return[3] // bottom front right
+	 *return[4] // top back left
+	 *return[5] // top back right
+	 *return[6] // bottom back left
+	 *return[7] // bottom back right
+	 */
+	public static Location[] getPlayerHeadGeometry(Location playerLocation, boolean isSneaking) {
+		Location[] headAnglesPoints = new Location[8];
+		
+		Location playerHeadRotationLocation = playerLocation.clone()
+				.add(0, isSneaking ? PLAYER_HEAD_ROTATION_HEIGHT_SNEAK : PLAYER_HEAD_ROTATION_HEIGHT, 0);
+		
+		DirectionalVector frontDirection = new DirectionalVector(playerHeadRotationLocation);
+		Vector frontHalfVector = frontDirection.toVector().multiply(PLAYER_HEAD_SIZE/2);
+		Vector backHalfDirection = frontDirection.getBackDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
+		Vector leftHalfVector = frontDirection.getLeftDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
+		Vector rightHalfVector = frontDirection.getRightDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
+		Vector topVector = frontDirection.getTopDirection().toVector().multiply(PLAYER_HEAD_SIZE);
+
+		Location bottomFrontMiddle = playerHeadRotationLocation.clone().add(frontHalfVector);
+		Location bottomBackMiddle = playerHeadRotationLocation.clone().add(backHalfDirection);
+		
+		Location topFrontMiddle = bottomFrontMiddle.clone().add(topVector);
+		Location topBackMiddle = bottomBackMiddle.clone().add(topVector);
+		
+		headAnglesPoints[0] = topFrontMiddle.clone().add(leftHalfVector);
+		headAnglesPoints[1] = topFrontMiddle.clone().add(rightHalfVector);
+		headAnglesPoints[2] = bottomFrontMiddle.clone().add(leftHalfVector);
+		headAnglesPoints[3] = bottomFrontMiddle.clone().add(rightHalfVector);
+		headAnglesPoints[4] = topBackMiddle.clone().add(leftHalfVector);
+		headAnglesPoints[5] = topBackMiddle.clone().add(rightHalfVector);
+		headAnglesPoints[6] = bottomBackMiddle.clone().add(leftHalfVector);
+		headAnglesPoints[7] = bottomBackMiddle.clone().add(rightHalfVector);
+		
+		return headAnglesPoints;
+	}
+	
+	
+	
+	
+	
+	/**
+	 * Check if the path from start location to end pass through
+	 * the axis aligned bounding box defined by min and max.
+	 * @param start
+	 * @param end
+	 * @param min
+	 * @param max
+	 * @return
+	 */
+    public static boolean hasIntersection(Vector start, Vector end, Vector min, Vector max) {
+        final double epsilon = 0.0001f;
+ 
+        Vector d = end.clone().subtract(start).multiply(0.5);
+        Vector e = max.clone().subtract(min).multiply(0.5);
+        Vector c = start.clone().add(d).subtract(min.clone().add(max).multiply(0.5));
+        Vector ad = d.clone();
+        ad.setX(Math.abs(ad.getX()));
+        ad.setY(Math.abs(ad.getY()));
+        ad.setZ(Math.abs(ad.getZ()));
+ 
+        if(Math.abs(c.getX()) > e.getX() + ad.getX()){
+            return false;
+        }
+ 
+        if(Math.abs(c.getY()) > e.getY() + ad.getY()){
+            return false;
+        }
+ 
+        if(Math.abs(c.getZ()) > e.getX() + ad.getZ()){
+            return false;
+        }
+ 
+        if(Math.abs(d.getY() * c.getZ() - d.getZ() * c.getY()) > e.getY() * ad.getZ() + e.getZ() * ad.getY() + epsilon){
+            return false;
+        }
+ 
+        if(Math.abs(d.getZ() * c.getX() - d.getX() * c.getZ()) > e.getZ() * ad.getX() + e.getX() * ad.getZ() + epsilon){
+            return false;
+        }
+ 
+        if(Math.abs(d.getX() * c.getY() - d.getY() * c.getX()) > e.getX() * ad.getY() + e.getY() * ad.getX() + epsilon){
+            return false;
+        }
+ 
+        return true;
+    }
+	
+	
+	
+	
+	
+	
+	
+	
+	
+	
+	
+	
+	
+	/**
+	 * This vector consider Minecraft X Y Z axis orientation,
+	 * but consider standard (not Minecraft) radian values for yaw and pitch.
+ * The length of this Vector (based on {@link #x}, {@link #y} and {@link #z} values) + * Is always 1. + * + *
Yaw :
+	 * North (-z) = -PI/2
+	 * East  (+x) = 0
+	 * South (+z) = PI/2
+	 * West  (-x) = ±PI
+	 * 
+	 * Pitch :
+	 * Up   (+y) = PI/2
+	 * Down (-y) = -PI/2
+ */ + public static class DirectionalVector { + /** + * The X cartesian coordinate of this {@link DirectionalVector}. + * It correspond to the X (west to east) axis in a Minecraft world. + */ + public final double x; + + /** + * The Y cartesian coordinate of this {@link DirectionalVector}. + * It correspond to the Y (bottom to top) axis in a Minecraft world. + */ + public final double y; + + /** + * The Z cartesian coordinate of this {@link DirectionalVector}. + * It correspond to the Z (north to south) axis in a Minecraft world. + */ + public final double z; + + /** + * The azimuthal angle φ (phi) of this {@link DirectionalVector}, in radian. + * It correspond with Minecraft world as follow : + *
Yaw :
+		 * North (-z) = -PI/2
+		 * East  (+x) = 0
+		 * South (+z) = PI/2
+		 * West  (-x) = ±PI
+ */ + public final double yaw; + + /** + * The polar angle θ (theta) of this {@link DirectionalVector}, in radian. + * It correspond with Minecraft world as follow : + *
Pitch :
+		 * Down (-y) = -PI/2
+		 * Up   (+y) = PI/2
+ */ + public final double pitch; + + /** + * Initialize this {@link DirectionalVector} with the yaw and pitch + * contained in the provided {@link Location}. + * {@link Location#getYaw()} and {@link Location#getPitch()} values are automatically + * converted to conform {@link #yaw} and {@link #pitch} specification. + * @param l + */ + public DirectionalVector(Location l) { + this( + Math.toRadians(((l.getYaw()+90)%360) > 180 ? ((l.getYaw()+90)%360)-360 : ((l.getYaw()+90)%360)), + -Math.toRadians(l.getPitch()) + ); + /* MC : +90 : %360 : >180 -> -360 + * South (+z) = 0, 360 : 90-450 : 90 : 90 : PI/2 + * West (-x) = 90 : 180 : 180 : ±180 : ±PI + * North (-z) = 180 : 270 : 270 : -90 : -PI/2 + * East (+x) = 270 : 360 : 0-360 : 0 : 0 + */ + } + + + + /** + * @param v the vector representing the direction. If v.getX() && v.getZ() are 0, + * the yaw will be 0. This may have inconsistence if the vector is calculated + * from a {@link Location}'s yaw and pitch. In this case, prefer using + * {@link #DirectionalVector(Location)}. The {@link Vector} is + * normalized if necessary (does not modify provided {@link Vector}). + */ + public DirectionalVector(Vector v) { + this(v.getX(), v.getY(), v.getZ()); + // this((v = v.clone().normalize()).getX(), v.getY(), v.getZ()); + } + + + + private DirectionalVector(double x, double y, double z) { + double vectSize = Math.sqrt(x*x + y*y + z*z); + this.x = x/vectSize; + this.y = y/vectSize; + this.z = z/vectSize; + + if (x == 0.0 && z == 0.0) { + pitch = y > 0.0 ? PId2 : -PId2; + yaw = 0; + } + else { + yaw = Math.atan2(z, x); + pitch = Math.atan(y / Math.sqrt(x*x + z*z)); + } + } + + private DirectionalVector(double x, double y, double z, double yaw, double pitch) { + this.x = x; + this.y = y; + this.z = z; + this.yaw = yaw; + this.pitch = pitch; + } + + private DirectionalVector(double yaw, double pitch) { + this.yaw = yaw; + this.pitch = pitch; + + y = Math.sin(pitch); + + double cosPitch = Math.cos(pitch); + x = cosPitch * Math.cos(yaw); + z = cosPitch * Math.sin(yaw); + + } + + + + + public Vector toVector() { + return new Vector(x, y, z); + } + + + /** + * Set the yaw and the pitch of the provided {@link Location} + * with the values inside the current {@link DirectionalVector} + * after conversion of these values + * @param l + */ + public void putIntoLocation(Location l) { + /* std : -PI/2 : <0 ? +2PI : MC + * South (+z) = PI/2 : 0 : 0 : 0, 360 + * West (-x) = ±PI : -3PI/2 - PI/2 : PI/2 : 90 + * North (-z) = -PI/2 : -PI : PI : 180 + * East (+x) = 0 : -PI/2 : 3PI/2 : 270 + */ + l.setYaw((float)Math.toDegrees(yaw < PId2 ? yaw + PIx2 - PId2 : yaw - PId2)); + l.setPitch((float)Math.toDegrees(-pitch)); + } + + + + + public DirectionalVector getOpposite() { + return new DirectionalVector( + -x, + -y, + -z, + (yaw > 0 ? (yaw - PI) : (yaw + PI)), + -pitch + ); + } + + /** + * If the current direction is the player face direction, + * this method return the direction of the back of the head. + * This is an alias of {@link #getOpposite()} + * @return + */ + public DirectionalVector getBackDirection() { + return getOpposite(); + } + + /** + * If the current direction is the player face direction, + * this method return the direction of the bottom of the head. + * @return + */ + public DirectionalVector getBottomDirection() { + return new DirectionalVector( + (pitch > 0 ? yaw : (yaw > 0 ? (yaw - PI) : (yaw + PI))), + (pitch > 0 ? (pitch - PId2) : (-PId2 - pitch)) + ); + } + + /** + * If the current direction is the player face direction, + * this method return the direction of the top of the head. + * @return + */ + public DirectionalVector getTopDirection() { + return new DirectionalVector( + (pitch < 0 ? yaw : (yaw > 0 ? (yaw - PI) : (yaw + PI))), + (pitch < 0 ? (pitch + PId2) : (PId2 - pitch)) + ); + } + + + + /** + * If the current direction is the player face direction, + * this method return the direction of the left of the head. + * @return + */ + public DirectionalVector getLeftDirection() { + return new DirectionalVector( + yaw > -PId2 ? (yaw - PId2) : (yaw - PId2 + PIx2), + 0 + ); + } + + + + /** + * If the current direction is the player face direction, + * this method return the direction of the right of the head. + * @return + */ + public DirectionalVector getRightDirection() { + return new DirectionalVector( + yaw < PId2 ? (yaw + PId2) : (yaw + PId2 - PIx2), + 0 + ); + } + + + + + } + + + + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/ItemStackBuilder.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/ItemStackBuilder.java new file mode 100644 index 0000000..8a7af8f --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/ItemStackBuilder.java @@ -0,0 +1,166 @@ +package fr.pandacube.lib.paper.util; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; + +import com.google.common.collect.Streams; + +import fr.pandacube.lib.core.chat.Chat; +import fr.pandacube.lib.core.chat.Chat.FormatableChat; +import net.md_5.bungee.api.chat.BaseComponent; + +public class ItemStackBuilder { + + public static ItemStackBuilder of(ItemStack base) { + return new ItemStackBuilder(base.clone()); + } + + public static ItemStackBuilder of(Material mat) { + return new ItemStackBuilder(new ItemStack(mat)); + } + + + + + + + + + + + + + private ItemStack stack; + private ItemMeta cachedMeta; + + private ItemStackBuilder(ItemStack base) { + stack = base; + } + + private ItemMeta getOrInitMeta() { + return (cachedMeta != null) ? cachedMeta : (cachedMeta = stack.getItemMeta()); + } + + private void updateMeta() { + stack.setItemMeta(cachedMeta); + } + + public ItemStackBuilder amount(int a) { + stack.setAmount(a); + return this; + } + + public ItemStackBuilder rawDisplayName(BaseComponent[] displayName) { + if (displayName != null) + getOrInitMeta().setDisplayNameComponent(displayName); + else + getOrInitMeta().setDisplayName(null); + updateMeta(); + return this; + } + + public ItemStackBuilder displayName(Chat displayName) { + if (displayName != null) { + if (displayName.get().isItalicRaw() == null) + ((FormatableChat)displayName).italic(false); + return rawDisplayName(displayName.getAsArray()); + } + else + return rawDisplayName(null); + } + + public ItemStackBuilder rawLore(List lore) { + getOrInitMeta().setLoreComponents(lore); + updateMeta(); + return this; + } + + public ItemStackBuilder lore(List lore) { + if (lore != null) { + return rawLore(lore.stream() + .map(line -> { + if (line.get().isItalicRaw() == null) + ((FormatableChat)line).italic(false); + return line.getAsArray(); + }) + .collect(Collectors.toList())); + } + else + return rawLore(Collections.emptyList()); + } + + public ItemStackBuilder addLoreAfter(List lore) { + if (lore != null) { + List baseLore = getOrInitMeta().getLoreComponents(); + if (baseLore == null) baseLore = Collections.emptyList(); + return rawLore( + Streams.concat( + baseLore.stream(), + lore.stream() + .map(line -> { + if (line.get().isItalicRaw() == null) + ((FormatableChat)line).italic(false); + return line.getAsArray(); + }) + ) + .collect(Collectors.toList())); + } + else + return this; + } + + public ItemStackBuilder enchant(Enchantment ench, int level) { + getOrInitMeta().addEnchant(ench, level, true); + updateMeta(); + return this; + } + + public ItemStackBuilder flags(ItemFlag... flags) { + getOrInitMeta().addItemFlags(flags); + updateMeta(); + return this; + } + + public ItemStackBuilder hideEnchants() { + return flags(ItemFlag.HIDE_ENCHANTS); + } + + public ItemStackBuilder hideAttributes() { + return flags(ItemFlag.HIDE_ATTRIBUTES); + } + + public ItemStackBuilder fakeEnchant() { + enchant(Enchantment.DURABILITY, 1); + return hideEnchants(); + } + + public ItemStackBuilder damage(int d) { + ItemMeta m = getOrInitMeta(); + if (m instanceof Damageable) + ((Damageable)m).setDamage(d); + updateMeta(); + return this; + } + + + + + + + + + + public ItemStack build() { + return stack; + } + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/MaterialUtil.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/MaterialUtil.java new file mode 100644 index 0000000..8b568ec --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/MaterialUtil.java @@ -0,0 +1,13 @@ +package fr.pandacube.lib.paper.util; + +import org.bukkit.Material; +import org.bukkit.block.data.type.Sign; +import org.bukkit.block.data.type.WallSign; + +public class MaterialUtil { + + public static boolean isSign(Material m) { + return WallSign.class.equals(m.data) || Sign.class.equals(m.data); + } + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/ScoreBoardUtil.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/ScoreBoardUtil.java new file mode 100644 index 0000000..787b000 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/ScoreBoardUtil.java @@ -0,0 +1,106 @@ +package fr.pandacube.lib.paper.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.bukkit.scoreboard.DisplaySlot; +import org.bukkit.scoreboard.Objective; +import org.bukkit.scoreboard.Score; +import org.bukkit.scoreboard.Scoreboard; + +import fr.pandacube.lib.core.chat.ChatUtil; +import net.md_5.bungee.api.ChatColor; + +public class ScoreBoardUtil { + + /** + * Met à jour la Sidebar d'un Scoreboard donné + * + * @param scBrd Le Scoreboard à mettre à jour (ne doit pas être null) + * @param title Le titre de la Sidebar, limité à 32 caractères + * @param lines Les lignes qui doivent être affichés. Si un éléments du + * tableau est null, il sera compté comme une chaine vide. Toutes les + * lignes seront rognés aux 40 premiers caractères + */ + public static void updateScoreboardSidebar(Scoreboard scBrd, String title, String[] lines) { + if (scBrd == null) throw new IllegalArgumentException("scBrd doit être non null"); + if (lines == null) lines = new String[0]; + + Objective obj = scBrd.getObjective("sidebar_autogen"); + if (obj != null && !obj.getCriteria().equalsIgnoreCase("dummy")) { + obj.unregister(); + obj = null; + } + + title = title == null ? "" : ChatUtil.truncateAtLengthWithoutReset(title, 32); + + if (obj == null) + obj = scBrd.registerNewObjective("sidebar_autogen", "dummy", title); + + if (!title.equals(obj.getDisplayName())) + obj.setDisplayName(title); + if (!DisplaySlot.SIDEBAR.equals(obj.getDisplaySlot())) + obj.setDisplaySlot(DisplaySlot.SIDEBAR); + + filterLines(lines); + + List listLines = Arrays.asList(lines); + + // remove lines that are not in the array + Objective fObj = obj; + scBrd.getEntries().stream() + .filter(e -> !listLines.contains(e)) + .filter(e -> fObj.getScore(e).isScoreSet()) + .forEach(scBrd::resetScores); + + // add/update others lines + int boardPos = lines.length; + for (String line : lines) { + if (line == null) line = ""; + + Score score = obj.getScore(line); + + if (score.getScore() != boardPos) + score.setScore(boardPos); + + boardPos--; + } + } + + /** + * Met à jour la Sidebar d'un Scoreboard donné + * + * @param scBrd Le Scoreboard à mettre à jour + * @param title Le titre de la Sidebar, limité à 32 caractères + * @param lines Les lignes qui doivent être affichés. Si un éléments du + * tableau est null, il sera compté comme une chaine vide. Toutes les + * lignes seront rognés aux 40 premiers caractères + */ + public static void updateScoreboardSidebar(Scoreboard scBrd, String title, List lines) { + updateScoreboardSidebar(scBrd, title, lines.toArray(new String[lines.size()])); + } + + + + + @SuppressWarnings("deprecation") + private static void filterLines(String[] lines) { + List previous = new ArrayList<>(); + for (int i = 0; i < lines.length; i++) { + String line = lines[i] == null ? "" : ChatUtil.truncateAtLengthWithoutReset(lines[i], 40); + if (previous.contains(line)) { + for (ChatColor c : ChatColor.values()) { + line = ChatUtil.truncateAtLengthWithoutReset(lines[i], 38) + c; + if (!previous.contains(line)) { + break; + } + } + } + lines[i] = line; + previous.add(lines[i]); + } + } + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java new file mode 100644 index 0000000..b861d65 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java @@ -0,0 +1,191 @@ +package fr.pandacube.lib.paper.util; + +import java.util.Base64; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; + +import fr.pandacube.lib.core.chat.Chat; + +/** + * Represents some special mob heads, also support creating player skulls and custom skulls. + * + * @author xigsag, SBPrime + * + * @see https://github.com/TigerHix/Hex-Utils/blob/9954159a323d12733b29c287a56980991cee2948/hex/util/Skull.java + */ +public enum Skull { + + ARROW_LEFT("MHF_ArrowLeft"), + ARROW_RIGHT("MHF_ArrowRight"), + ARROW_UP("MHF_ArrowUp"), + ARROW_DOWN("MHF_ArrowDown"), + QUESTION("MHF_Question"), + EXCLAMATION("MHF_Exclamation"), + CAMERA("FHG_Cam"), + + ZOMBIE_PIGMAN("MHF_PigZombie"), + PIG("MHF_Pig"), + SHEEP("MHF_Sheep"), + BLAZE("MHF_Blaze"), + CHICKEN("MHF_Chicken"), + COW("MHF_Cow"), + SLIME("MHF_Slime"), + SPIDER("MHF_Spider"), + SQUID("MHF_Squid"), + VILLAGER("MHF_Villager"), + OCELOT("MHF_Ocelot"), + HEROBRINE("MHF_Herobrine"), + LAVA_SLIME("MHF_LavaSlime"), + MOOSHROOM("MHF_MushroomCow"), + GOLEM("MHF_Golem"), + GHAST("MHF_Ghast"), + ENDERMAN("MHF_Enderman"), + CAVE_SPIDER("MHF_CaveSpider"), + + CACTUS("MHF_Cactus"), + CAKE("MHF_Cake"), + CHEST("MHF_Chest"), + MELON("MHF_Melon"), + LOG("MHF_OakLog"), + PUMPKIN("MHF_Pumpkin"), + TNT("MHF_TNT"), + DYNAMITE("MHF_TNT2"); + + private String name; + + private Skull(String mcName) { + name = mcName; + } + + /** + * Return the item based on this Skull enum. + * + * @return itemstack + */ + public ItemStack get() { + return get(null, null); + } + /** + * Return the item based on this Skull enum, with the provided display name and lore. + * + * @return itemstack + */ + public ItemStack get(Chat dispName, List lore) { + return getFromPlayerName(name, dispName, lore); + } + + + + /** + * Return a skull of a player based on his name. + * + * @param name player's name + * @return itemstack + */ + public static ItemStack getFromPlayerName(String name, Chat dispName, List lore) { + ItemStack itemStack = new ItemStack(Material.PLAYER_HEAD, 1); + SkullMeta meta = (SkullMeta) itemStack.getItemMeta(); + + @SuppressWarnings({ "deprecation", "unused" }) + boolean b = meta.setOwner(name); + + if (dispName != null) + meta.setDisplayNameComponent(dispName.getAsArray()); + + if (lore != null) + meta.setLoreComponents(lore.stream().map(c -> c.getAsArray()).collect(Collectors.toList())); + + itemStack.setItemMeta(meta); + return itemStack; + } + + + + + + + + + + + + /** + * Return a skull that has a custom texture specified by url. + * + * @param url skin url + * @return itemstack + */ + public static ItemStack getFromSkinURL(String url) { + return getFromSkinURL(url, null, null); + } + + /** + * Return a skull that has a custom texture specified by url. + * + * @param url skin url + * @return itemstack + */ + public static ItemStack getFromSkinURL(String url, Chat name, List lore) { + return getFromBase64String(Base64.getEncoder().encodeToString(String.format("{textures:{SKIN:{url:\"%s\"}}}", url).getBytes()), name, lore); + } + + + + + + + + + /** + * Return a skull that has a custom texture specified by a base64 String. + * + * @param str the base64 string from gameprofile informations + * @return itemstack + */ + public static ItemStack getFromBase64String(String str) { + return getFromBase64String(str, null, null); + } + + + /** + * Return a skull that has a custom texture specified by a base64 String. + * + * @param str the base64 string from gameprofile informations + * @return itemstack + */ + public static ItemStack getFromBase64String(String str, Chat dispName, List lore) { + ItemStack head = new ItemStack(Material.PLAYER_HEAD, 1); + + SkullMeta headMeta = (SkullMeta) head.getItemMeta(); + + PlayerProfile profile = Bukkit.createProfile(UUID.randomUUID()); + profile.setProperty(new ProfileProperty("textures", str)); + headMeta.setPlayerProfile(profile); + + if (dispName != null) + headMeta.setDisplayNameComponent(dispName.getAsArray()); + + if (lore != null) + headMeta.setLoreComponents(lore.stream().map(c -> c.getAsArray()).collect(Collectors.toList())); + + head.setItemMeta(headMeta); + + return head; + } + + + + + + +} + \ No newline at end of file diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/ThreadUtil.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/ThreadUtil.java new file mode 100644 index 0000000..27a46d1 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/ThreadUtil.java @@ -0,0 +1,35 @@ +package fr.pandacube.lib.paper.util; + +import java.util.concurrent.Callable; + +import org.bukkit.Bukkit; + +import fr.pandacube.lib.paper.PandaLibPaper; + +public class ThreadUtil { + + public static void runOnServerThread(Runnable task) { + if (Bukkit.isPrimaryThread()) + task.run(); + + Bukkit.getScheduler().runTask(PandaLibPaper.getPlugin(), task); + } + + public static T runOnServerThreadAndWait(Callable task) throws Exception { + if (Bukkit.isPrimaryThread()) + return task.call(); + + return Bukkit.getScheduler().callSyncMethod(PandaLibPaper.getPlugin(), task).get(); + } + + public static void runOnServerThreadAndWait(Runnable task) throws Exception { + runOnServerThreadAndWait((Callable)() -> { + task.run(); + return null; + }); + } + + + + +} diff --git a/pom.xml b/pom.xml index 162147e..af5b7e9 100644 --- a/pom.xml +++ b/pom.xml @@ -40,13 +40,16 @@ 11 11 UTF-8 + 1.16-R0.4-SNAPSHOT + 1.16.5-R0.1 + 1.16.5 Core - + + Paper