Refactoring
This commit is contained in:
parent
87f2bf0b19
commit
5019788865
17
.project
Normal file
17
.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>pandalib-parent</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
2
.settings/org.eclipse.core.resources.prefs
Normal file
2
.settings/org.eclipse.core.resources.prefs
Normal file
@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
4
.settings/org.eclipse.m2e.core.prefs
Normal file
4
.settings/org.eclipse.m2e.core.prefs
Normal file
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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<Chat> prefix;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.text_display;
|
||||
package fr.pandacube.lib.core.chat;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
@ -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;
|
@ -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);
|
||||
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.text_display;
|
||||
package fr.pandacube.lib.core.chat;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
@ -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 {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.commands;
|
||||
package fr.pandacube.lib.core.commands;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
@ -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<S> {
|
@ -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
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.config;
|
||||
package fr.pandacube.lib.core.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
@ -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;
|
||||
|
||||
/**
|
||||
* <b>ORM = Object-Relational Mapping</b>
|
||||
* 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<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
|
||||
private static Map<Class<? extends SQLElement<?>>, String> tableNames = new HashMap<>();
|
||||
|
||||
private static DBConnection connection;
|
||||
/* package */ static String tablePrefix = "";
|
||||
|
||||
public static DBConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public synchronized static <E extends SQLElement<E>> void init(DBConnection conn) {
|
||||
|
||||
public synchronized static <E extends SQLElement<E>> void init(DBConnection conn, String tablePrefix) {
|
||||
connection = conn;
|
||||
|
||||
|
||||
DB.tablePrefix = Objects.requireNonNull(tablePrefix);
|
||||
}
|
||||
|
||||
public static synchronized <E extends SQLElement<E>> void initTable(Class<E> elemClass) throws ORMInitTableException {
|
||||
public static synchronized <E extends SQLElement<E>> void initTable(Class<E> 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 <E extends SQLElement<E>> void createTable(E elem) throws SQLException {
|
||||
|
||||
String sql = "CREATE TABLE IF NOT EXISTS " + elem.tableName() + " (";
|
||||
String tableName = tablePrefix + elem.tableName();
|
||||
|
||||
String sql = "CREATE TABLE IF NOT EXISTS " + tableName + " (";
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
Collection<SQLField<E, ?>> tableFields = elem.getFields().values();
|
||||
@ -82,7 +89,7 @@ public final class ORM {
|
||||
}
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> String getTableName(Class<E> elemClass) throws ORMException {
|
||||
public static <E extends SQLElement<E>> String getTableName(Class<E> elemClass) throws DBException {
|
||||
initTable(elemClass);
|
||||
return tableNames.get(elemClass);
|
||||
}
|
||||
@ -97,91 +104,91 @@ public final class ORM {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E extends SQLElement<E>> SQLField<E, Integer> getSQLIdField(Class<E> elemClass)
|
||||
throws ORMInitTableException {
|
||||
throws DBInitTableException {
|
||||
initTable(elemClass);
|
||||
return (SQLField<E, Integer>) SQLElement.fieldsCache.get(elemClass).get("id");
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Integer... ids) throws ORMException {
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Integer... ids) throws DBException {
|
||||
return getByIds(elemClass, Arrays.asList(ids));
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Collection<Integer> ids)
|
||||
throws ORMException {
|
||||
throws DBException {
|
||||
return getAll(elemClass, getSQLIdField(elemClass).in(ids), SQLOrderBy.asc(getSQLIdField(elemClass)), 1, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getById(Class<E> elemClass, int id) throws ORMException {
|
||||
public static <E extends SQLElement<E>> E getById(Class<E> elemClass, int id) throws DBException {
|
||||
return getFirst(elemClass, getSQLIdField(elemClass).eq(id));
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where)
|
||||
throws ORMException {
|
||||
throws DBException {
|
||||
return getFirst(elemClass, where, null, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLOrderBy<E> orderBy)
|
||||
throws ORMException {
|
||||
throws DBException {
|
||||
return getFirst(elemClass, null, orderBy, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy)
|
||||
throws ORMException {
|
||||
throws DBException {
|
||||
return getFirst(elemClass, where, orderBy, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy, Integer offset)
|
||||
throws ORMException {
|
||||
throws DBException {
|
||||
SQLElementList<E> elts = getAll(elemClass, where, orderBy, 1, offset);
|
||||
return (elts.size() == 0) ? null : elts.get(0);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass) throws ORMException {
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass) throws DBException {
|
||||
return getAll(elemClass, null, null, null, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where) throws ORMException {
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where) throws DBException {
|
||||
return getAll(elemClass, where, null, null, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy) throws ORMException {
|
||||
SQLOrderBy<E> orderBy) throws DBException {
|
||||
return getAll(elemClass, where, orderBy, null, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy, Integer limit) throws ORMException {
|
||||
SQLOrderBy<E> orderBy, Integer limit) throws DBException {
|
||||
return getAll(elemClass, where, orderBy, limit, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy, Integer limit, Integer offset) throws ORMException {
|
||||
SQLOrderBy<E> orderBy, Integer limit, Integer offset) throws DBException {
|
||||
SQLElementList<E> elmts = new SQLElementList<>();
|
||||
forEach(elemClass, where, orderBy, limit, offset, elmts::add);
|
||||
return elmts;
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, Consumer<E> action) throws ORMException {
|
||||
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, Consumer<E> action) throws DBException {
|
||||
forEach(elemClass, null, null, null, null, action);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
|
||||
Consumer<E> action) throws ORMException {
|
||||
Consumer<E> action) throws DBException {
|
||||
forEach(elemClass, where, null, null, null, action);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy, Consumer<E> action) throws ORMException {
|
||||
SQLOrderBy<E> orderBy, Consumer<E> action) throws DBException {
|
||||
forEach(elemClass, where, orderBy, null, null, action);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy, Integer limit, Consumer<E> action) throws ORMException {
|
||||
SQLOrderBy<E> orderBy, Integer limit, Consumer<E> action) throws DBException {
|
||||
forEach(elemClass, where, orderBy, limit, null, action);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy, Integer limit, Integer offset, Consumer<E> action) throws ORMException {
|
||||
SQLOrderBy<E> orderBy, Integer limit, Integer offset, Consumer<E> 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 <E extends SQLElement<E>> long count(Class<E> elemClass) throws ORMException {
|
||||
public static <E extends SQLElement<E>> long count(Class<E> elemClass) throws DBException {
|
||||
return count(elemClass, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> long count(Class<E> elemClass, SQLWhere<E> where) throws ORMException {
|
||||
public static <E extends SQLElement<E>> long count(Class<E> elemClass, SQLWhere<E> 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<Object> params) throws ORMException {
|
||||
public static ResultSet customQueryStatement(String sql, List<Object> 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 <E extends SQLElement<E>> SQLUpdate<E> update(Class<E> elemClass, SQLWhere<E> where) throws ORMException {
|
||||
public static <E extends SQLElement<E>> SQLUpdate<E> update(Class<E> elemClass, SQLWhere<E> where) throws DBException {
|
||||
return new SQLUpdate<>(elemClass, where);
|
||||
}
|
||||
|
||||
/* package */ static <E extends SQLElement<E>> int update(Class<E> elemClass, SQLWhere<E> where, Map<SQLField<E, ?>, Object> values) throws ORMException {
|
||||
/* package */ static <E extends SQLElement<E>> int update(Class<E> elemClass, SQLWhere<E> where, Map<SQLField<E, ?>, 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 <E extends SQLElement<E>> int delete(Class<E> elemClass, SQLWhere<E> where) throws ORMException {
|
||||
public static <E extends SQLElement<E>> int delete(Class<E> elemClass, SQLWhere<E> where) throws DBException {
|
||||
initTable(elemClass);
|
||||
|
||||
if (where == null) {
|
||||
@ -308,7 +315,7 @@ public final class ORM {
|
||||
|
||||
|
||||
|
||||
public static int customUpdateStatement(String sql, List<Object> params) throws ORMException {
|
||||
public static int customUpdateStatement(String sql, List<Object> 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 <E extends SQLElement<E>> int truncateTable(Class<E> elemClass) throws ORMException {
|
||||
public static <E extends SQLElement<E>> int truncateTable(Class<E> 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 extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws ORMException {
|
||||
private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> 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<Object, Object>)sqlField.type).dbToJavaConv.apply(val);
|
||||
} catch (Exception e) {
|
||||
throw new ORMException("Error while converting value of field '"+sqlField.getName()+"' with SQLCustomType from "+((SQLCustomType<Object, Object>)sqlField.type).intermediateJavaType
|
||||
throw new DBException("Error while converting value of field '"+sqlField.getName()+"' with SQLCustomType from "+((SQLCustomType<Object, Object>)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() {}
|
||||
|
||||
}
|
@ -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
|
18
Core/src/main/java/fr/pandacube/lib/core/db/DBException.java
Normal file
18
Core/src/main/java/fr/pandacube/lib/core/db/DBException.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package fr.pandacube.lib.core.db;
|
||||
|
||||
public class DBInitTableException extends DBException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem) {
|
||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"));
|
||||
}
|
||||
|
||||
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem, Throwable t) {
|
||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"), t);
|
||||
}
|
||||
|
||||
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem, String message) {
|
||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null") + ": " + message);
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.orm;
|
||||
package fr.pandacube.lib.core.db;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
@ -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<E extends SQLElement<E>> {
|
||||
/** cache for fields for each subclass of SQLElement */
|
||||
/* package */ static final Map<Class<? extends SQLElement<?>>, SQLFieldMap<? extends SQLElement<?>>> 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<E extends SQLElement<E>> {
|
||||
public SQLElement() {
|
||||
|
||||
try {
|
||||
ORM.initTable((Class<E>)getClass());
|
||||
} catch (ORMInitTableException e) {
|
||||
DB.initTable((Class<E>)getClass());
|
||||
} catch (DBInitTableException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @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<E extends SQLElement<E>> {
|
||||
* @param <P> 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 <T, P extends SQLElement<P>> P getReferencedEntry(SQLFKField<E, T, P> field) throws ORMException {
|
||||
public <T, P extends SQLElement<P>> P getReferencedEntry(SQLFKField<E, T, P> 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<E extends SQLElement<E>> {
|
||||
* @param <F> 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 <T, F extends SQLElement<F>> SQLElementList<F> getReferencingForeignEntries(SQLFKField<F, T, E> field, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws ORMException {
|
||||
public <T, F extends SQLElement<F>> SQLElementList<F> getReferencingForeignEntries(SQLFKField<F, T, E> field, SQLOrderBy<F> 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<E extends SQLElement<E>> {
|
||||
}
|
||||
|
||||
@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<E>)getClass());
|
||||
DB.initTable((Class<E>)getClass());
|
||||
try {
|
||||
|
||||
if (stored) { // mettre à jour les valeurs dans la base
|
||||
@ -243,7 +243,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
|
||||
|
||||
if (modifiedValues.isEmpty()) return (E) this;
|
||||
|
||||
ORM.update((Class<E>)getClass(), getFieldId().eq(getId()), modifiedValues);
|
||||
DB.update((Class<E>)getClass(), getFieldId().eq(getId()), modifiedValues);
|
||||
}
|
||||
else { // ajouter dans la base
|
||||
|
||||
@ -268,7 +268,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
|
||||
}
|
||||
|
||||
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<E extends SQLElement<E>> {
|
||||
|
||||
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 <E extends SQLElement<E>> void addValueToSQLObjectList(List<Object> list, SQLField<E, ?> field, Object jValue) throws ORMException {
|
||||
protected static <E extends SQLElement<E>> void addValueToSQLObjectList(List<Object> list, SQLField<E, ?> 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<E extends SQLElement<E>> {
|
||||
return (SQLField<E, Integer>) 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<E extends SQLElement<E>> extends ArrayList<E> {
|
||||
*
|
||||
* @throws SQLException
|
||||
*/
|
||||
public synchronized int saveCommon() throws ORMException {
|
||||
public synchronized int saveCommon() throws DBException {
|
||||
List<E> storedEl = getStoredEl();
|
||||
if (storedEl.isEmpty()) return 0;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<E> classEl = (Class<E>)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<E extends SQLElement<E>> extends ArrayList<E> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @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<E extends SQLElement<E>> extends ArrayList<E> {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<E> classEl = (Class<E>)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<E extends SQLElement<E>> extends ArrayList<E> {
|
||||
|
||||
|
||||
|
||||
public <T, P extends SQLElement<P>> SQLElementList<P> getReferencedEntries(SQLFKField<E, T, P> foreignKey, SQLOrderBy<P> orderBy) throws ORMException {
|
||||
public <T, P extends SQLElement<P>> SQLElementList<P> getReferencedEntries(SQLFKField<E, T, P> foreignKey, SQLOrderBy<P> orderBy) throws DBException {
|
||||
Set<T> values = new HashSet<>();
|
||||
forEach(v -> {
|
||||
T val = v.get(foreignKey);
|
||||
@ -143,12 +143,12 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
|
||||
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 <T, P extends SQLElement<P>> Map<T, P> getReferencedEntriesInGroups(SQLFKField<E, T, P> foreignKey) throws ORMException {
|
||||
public <T, P extends SQLElement<P>> Map<T, P> getReferencedEntriesInGroups(SQLFKField<E, T, P> foreignKey) throws DBException {
|
||||
SQLElementList<P> foreignElemts = getReferencedEntries(foreignKey, null);
|
||||
|
||||
Map<T, P> ret = new HashMap<>();
|
||||
@ -158,7 +158,7 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
|
||||
|
||||
|
||||
|
||||
public <T, F extends SQLElement<F>> SQLElementList<F> getReferencingForeignEntries(SQLFKField<F, T, E> foreignKey, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws ORMException {
|
||||
public <T, F extends SQLElement<F>> SQLElementList<F> getReferencingForeignEntries(SQLFKField<F, T, E> foreignKey, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws DBException {
|
||||
Set<T> values = new HashSet<>();
|
||||
forEach(v -> {
|
||||
T val = v.get(foreignKey.getPrimaryField());
|
||||
@ -170,12 +170,12 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
|
||||
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 <T, F extends SQLElement<F>> Map<T, SQLElementList<F>> getReferencingForeignEntriesInGroups(SQLFKField<F, T, E> foreignKey, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws ORMException {
|
||||
public <T, F extends SQLElement<F>> Map<T, SQLElementList<F>> getReferencingForeignEntriesInGroups(SQLFKField<F, T, E> foreignKey, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws DBException {
|
||||
SQLElementList<F> foreignElements = getReferencingForeignEntries(foreignKey, orderBy, limit, offset);
|
||||
|
||||
Map<T, SQLElementList<F>> map = new HashMap<>();
|
@ -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<F extends SQLElement<F>, T, P extends SQLElement<P>> ext
|
||||
/* package */ static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Integer deflt, Class<F> fkEl) {
|
||||
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
|
||||
try {
|
||||
SQLField<F, Integer> f = ORM.getSQLIdField(fkEl);
|
||||
SQLField<F, Integer> 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<F extends SQLElement<F>, T, P extends SQLElement<P>> ext
|
||||
private void construct(Class<P> fkEl, SQLField<P, T> 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);
|
||||
}
|
||||
|
@ -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<E extends SQLElement<E>, T> {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.orm;
|
||||
package fr.pandacube.lib.core.db;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.orm;
|
||||
package fr.pandacube.lib.core.db;
|
||||
|
||||
public class SQLType<T> {
|
||||
|
@ -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<E extends SQLElement<E>> {
|
||||
|
||||
@ -37,14 +37,14 @@ public class SQLUpdate<E extends SQLElement<E>> {
|
||||
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<Object> params = new ArrayList<>();
|
||||
|
||||
boolean first = true;
|
||||
@ -64,7 +64,7 @@ public class SQLUpdate<E extends SQLElement<E>> {
|
||||
|
||||
sql += ";";
|
||||
|
||||
return ORM.customUpdateStatement(sql, params);
|
||||
return DB.customUpdateStatement(sql, params);
|
||||
}
|
||||
|
||||
}
|
306
Core/src/main/java/fr/pandacube/lib/core/db/SQLWhere.java
Normal file
306
Core/src/main/java/fr/pandacube/lib/core/db/SQLWhere.java
Normal file
@ -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<E extends SQLElement<E>> {
|
||||
|
||||
public abstract Pair<String, List<Object>> 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<E> and(SQLWhere<E> other) {
|
||||
return new SQLWhereAnd<E>().and(this).and(other);
|
||||
}
|
||||
|
||||
public SQLWhereOr<E> or(SQLWhere<E> other) {
|
||||
return new SQLWhereOr<E>().or(this).or(other);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLWhereAnd<E> and() {
|
||||
return new SQLWhereAnd<>();
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLWhereOr<E> or() {
|
||||
return new SQLWhereOr<>();
|
||||
}
|
||||
|
||||
public static String escapeLike(String str) {
|
||||
return str.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static abstract class SQLWhereChain<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLBoolOp operator;
|
||||
protected List<SQLWhere<E>> conditions = new ArrayList<>();
|
||||
|
||||
private SQLWhereChain(SQLBoolOp op) {
|
||||
if (op == null) throw new IllegalArgumentException("op can't be null");
|
||||
operator = op;
|
||||
}
|
||||
|
||||
protected void add(SQLWhere<E> sqlWhere) {
|
||||
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
|
||||
conditions.add(sqlWhere);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() throws DBException {
|
||||
if (conditions.isEmpty()) {
|
||||
throw new DBException("SQLWhereChain needs at least one element inside !");
|
||||
}
|
||||
|
||||
String sql = "";
|
||||
List<Object> params = new ArrayList<>();
|
||||
boolean first = true;
|
||||
|
||||
for (SQLWhere<E> w : conditions) {
|
||||
if (!first) sql += " " + operator.sql + " ";
|
||||
first = false;
|
||||
|
||||
Pair<String, List<Object>> ret = w.toSQL();
|
||||
sql += "(" + ret.getValue0() + ")";
|
||||
params.addAll(ret.getValue1());
|
||||
}
|
||||
|
||||
return new Pair<>(sql, params);
|
||||
}
|
||||
|
||||
protected enum SQLBoolOp {
|
||||
/** Equivalent to SQL "<code>AND</code>" */
|
||||
AND("AND"),
|
||||
/** Equivalent to SQL "<code>OR</code>" */
|
||||
OR("OR");
|
||||
/* package */ final String sql;
|
||||
|
||||
private SQLBoolOp(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class SQLWhereAnd<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
||||
|
||||
private SQLWhereAnd() {
|
||||
super(SQLBoolOp.AND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLWhereAnd<E> and(SQLWhere<E> other) {
|
||||
add(other);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class SQLWhereOr<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
||||
|
||||
private SQLWhereOr() {
|
||||
super(SQLBoolOp.OR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLWhereOr<E> or(SQLWhere<E> other) {
|
||||
add(other);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static class SQLWhereComp<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> 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 */ <T> SQLWhereComp(SQLField<E, T> 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<String, List<Object>> toSQL() throws DBException {
|
||||
List<Object> params = new ArrayList<>();
|
||||
SQLElement.addValueToSQLObjectList(params, left, right);
|
||||
return new Pair<>("`" + left.getName() + "` " + comp.sql + " ? ", params);
|
||||
}
|
||||
|
||||
/* package */ enum SQLComparator {
|
||||
/** Equivalent to SQL "<code>=</code>" */
|
||||
EQ("="),
|
||||
/** Equivalent to SQL "<code>></code>" */
|
||||
GT(">"),
|
||||
/** Equivalent to SQL "<code>>=</code>" */
|
||||
GEQ(">="),
|
||||
/** Equivalent to SQL "<code><</code>" */
|
||||
LT("<"),
|
||||
/** Equivalent to SQL "<code><=</code>" */
|
||||
LEQ("<="),
|
||||
/** Equivalent to SQL "<code>!=</code>" */
|
||||
NEQ("!=");
|
||||
|
||||
/* package */ final String sql;
|
||||
|
||||
private SQLComparator(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static class SQLWhereIn<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> field;
|
||||
private Collection<?> values;
|
||||
|
||||
/* package */ <T> SQLWhereIn(SQLField<E, T> f, Collection<T> 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<String, List<Object>> toSQL() throws DBException {
|
||||
List<Object> 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<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> 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<E, ?> 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<String, List<Object>> toSQL() {
|
||||
ArrayList<Object> params = new ArrayList<>();
|
||||
params.add(likeExpr);
|
||||
return new Pair<>("`" + field.getName() + "` LIKE ? ", params);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static class SQLWhereNull<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> 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<E, ?> 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<String, List<Object>> toSQL() {
|
||||
return new Pair<>("`" + fild.getName() + "` IS " + ((nulll) ? "NULL" : "NOT NULL"), new ArrayList<>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.net;
|
||||
package fr.pandacube.lib.core.net;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.net;
|
||||
package fr.pandacube.lib.core.net;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.net;
|
||||
package fr.pandacube.lib.core.net;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.net;
|
||||
package fr.pandacube.lib.core.net;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PPacketListener<P extends PPacket> {
|
@ -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")
|
@ -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}
|
||||
@ -36,6 +35,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;
|
||||
private SocketAddress addr;
|
||||
@ -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;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.net;
|
||||
package fr.pandacube.lib.core.net;
|
||||
|
||||
public interface PSocketConnectionListener {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.network_api.client;
|
||||
package fr.pandacube.lib.core.network_api.client;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
@ -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;
|
@ -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;
|
@ -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 {
|
@ -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;
|
@ -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
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.network_api.server;
|
||||
package fr.pandacube.lib.core.network_api.server;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
@ -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<PermGroup> getInheritances();
|
||||
public abstract List<String> 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<ServerWorldKey> getSelfPermissionsServerWorldKeys() {
|
||||
return getBackendEntity().getSelfPermissionsServerWorldKeys();
|
||||
}
|
||||
|
||||
public List<String> getSelfPermissions() {
|
||||
return getBackendEntity().getSelfPermissions(null, null);
|
||||
}
|
||||
|
||||
public List<String> getSelfPermissions(String server) {
|
||||
return getBackendEntity().getSelfPermissions(server, null);
|
||||
}
|
||||
|
||||
public List<String> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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<PermGroup> getInheritances() {
|
||||
return fromCachedGroups(getBackendEntity().inheritances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> 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<PermGroup> fromCachedGroups(List<CachedGroup> in) {
|
||||
return in.stream()
|
||||
.map(cg -> Permissions.getGroup(cg.name))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
@ -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<PermGroup> getInheritances() {
|
||||
return PermGroup.fromCachedGroups(getBackendEntity().groups);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> 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<PermGroup> getGroups() {
|
||||
return getInheritances();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for {@link #getInheritances()}.
|
||||
* @return
|
||||
*/
|
||||
public List<String> 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);
|
||||
}
|
||||
}
|
@ -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<PermGroup> getGroups() {
|
||||
checkInitialized();
|
||||
return PermGroup.fromCachedGroups(backendReader.getGroups());
|
||||
}
|
||||
|
||||
public static List<PermGroup> getDefaultGroups() {
|
||||
checkInitialized();
|
||||
return PermGroup.fromCachedGroups(backendReader.getDefaultGroups());
|
||||
}
|
||||
|
||||
public static List<String> getFullPermissionsList() {
|
||||
return backendReader.getFullPermissionsList();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<UUID, CachedPlayer> usersCache = CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.build();
|
||||
private Set<String> fullPermissionsList = new TreeSet<String>();
|
||||
|
||||
/* package */ synchronized List<String> 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<SQLPermissions> playerData = DB.getAll(SQLPermissions.class,
|
||||
SQLPermissions.type.eq(EntityType.User.getCode())
|
||||
.and(SQLPermissions.name.like(playerId.toString()))
|
||||
);
|
||||
|
||||
Map<String, List<SQLPermissions>> 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<ServerWorldKey, List<String>> 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<String, CachedGroup> 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<CachedGroup> getDefaultGroups() {
|
||||
return groupsCache.values().stream()
|
||||
.filter(g -> g.deflt)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<CachedGroup> 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<String, CachedGroup> newData = new LinkedHashMap<>();
|
||||
Set<String> newFullPermissionsList = new TreeSet<>();
|
||||
|
||||
SQLElementList<SQLPermissions> groupData = DB.getAll(SQLPermissions.class, SQLPermissions.type.eq(EntityType.Group.getCode()));
|
||||
|
||||
Map<String, Map<String, List<SQLPermissions>>> 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<String, Map<String, List<SQLPermissions>>> groupsRawData, Map<String, CachedGroup> newData, Set<String> newFullPermissionsList) {
|
||||
if (newData.containsKey(groupName))
|
||||
return;
|
||||
|
||||
Map<String, List<SQLPermissions>> 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<ServerWorldKey, List<String>> 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<ServerWorldKey, List<String>> selfPermissions;
|
||||
|
||||
private CachedEntity(String n, String p, String s,
|
||||
Map<ServerWorldKey, List<String>> perms) {
|
||||
name = n; selfPrefix = p; selfSuffix = s; selfPermissions = perms;
|
||||
}
|
||||
|
||||
/* package */ List<String> getSelfPermissions(String server, String world) {
|
||||
return selfPermissions.getOrDefault(new ServerWorldKey(server, world), new ArrayList<>());
|
||||
}
|
||||
|
||||
/* package */ Set<ServerWorldKey> 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<CachedGroup> groups = new ArrayList<>();
|
||||
public boolean usingDefaultGroups = false;
|
||||
private CachedPlayer(UUID pl, String p, String s,
|
||||
Map<ServerWorldKey, List<String>> perms) {
|
||||
super(pl.toString(), p, s, perms);
|
||||
playerId = pl;
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ static class CachedGroup extends CachedEntity {
|
||||
public final boolean deflt;
|
||||
public final List<CachedGroup> inheritances = new ArrayList<>();
|
||||
private CachedGroup(String n, String p, String s,
|
||||
boolean dflt, Map<ServerWorldKey, List<String>> perms) {
|
||||
super(n, p, s, perms);
|
||||
deflt = dflt;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<DataCacheKey, String> 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<CachedGroup> inheritances = resolutionNode.entity instanceof CachedPlayer
|
||||
? ((CachedPlayer)resolutionNode.entity).groups
|
||||
: ((CachedGroup)resolutionNode.entity).inheritances;
|
||||
|
||||
List<DataResolutionNode> 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<String> 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<DataResolutionNode> 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<String> getter;
|
||||
private DataType(CachedEntityGetter<String> g) {
|
||||
getter = g;
|
||||
}
|
||||
}
|
||||
|
||||
private interface CachedEntityGetter<R> {
|
||||
R apply(CachedEntity a);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private Cache<PermCacheKey, PermState> 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<CachedGroup> 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<PermResolutionNode> 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<PermState> 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<ParsedSelfPermission> 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.<staffBaseGroup>' 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.<groupName>' is deprecated. Use 'pandacube.ingroup.<groupName>' 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<ParsedSelfPermission> selfPermissions = new ArrayList<>();
|
||||
final List<PermResolutionNode> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<SQLPermissions> {
|
||||
|
||||
public SQLPermissions() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SQLPermissions(int id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String tableName() {
|
||||
return "pandacube_permissions";
|
||||
}
|
||||
|
||||
public static final SQLField<SQLPermissions, String> name = field(VARCHAR(64), false);
|
||||
public static final SQLField<SQLPermissions, Integer> type = field(TINYINT, false);
|
||||
public static final SQLField<SQLPermissions, String> key = field(VARCHAR(256), false);
|
||||
public static final SQLField<SQLPermissions, String> value = field(VARCHAR(256), false);
|
||||
public static final SQLField<SQLPermissions, String> server = field(VARCHAR(64), true);
|
||||
public static final SQLField<SQLPermissions, String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package fr.pandacube.lib.core.permissions;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ServerWorldKey implements Comparable<ServerWorldKey> {
|
||||
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<String> compStrNullFirst = Comparator.nullsFirst(String::compareToIgnoreCase);
|
||||
return Comparator.comparing((ServerWorldKey k) -> k.server, compStrNullFirst)
|
||||
.thenComparing(k -> k.world, compStrNullFirst)
|
||||
.compare(this, o);
|
||||
}
|
||||
}
|
247
Core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java
Normal file
247
Core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java
Normal file
@ -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 <i>true</i> if this player is part of the group,
|
||||
* <i>false</i> 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -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.
|
||||
* <br>
|
||||
* 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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<OP extends IOnlinePlayer, OF extends IOffPlayer> {
|
||||
private static IPlayerManager<?, ?> instance;
|
||||
public static synchronized IPlayerManager<?, ?> getInstance() { return instance; }
|
||||
|
||||
|
||||
private Map<UUID, OP> onlinePlayers = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
private LoadingCache<UUID, OF> 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<OP> getAll() {
|
||||
return new ArrayList<>(onlinePlayers.values());
|
||||
}
|
||||
|
||||
public List<OP> getAllNotVanished() {
|
||||
List<OP> 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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)}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||
* {@link #broadcast(BaseComponent, boolean, String, UUID)}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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)}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||
* {@link #broadcast(BaseComponent, boolean, UUID)}.
|
||||
* <p>
|
||||
* 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)}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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)}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||
* {@link #broadcast(BaseComponent, boolean, String, UUID)}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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)}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||
* {@link #broadcast(BaseComponent, boolean, UUID)}.
|
||||
* <p>
|
||||
* 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)}.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -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<UUID, String> playerLastKnownName = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(2, TimeUnit.MINUTES)
|
||||
.maximumSize(1000)
|
||||
.build();
|
||||
|
||||
private static Cache<Pair<String, Boolean>, 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<SearchResponseProfile> 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 <S> SuggestionsSupplier<S> TAB_PLAYER_OFFLINE() {
|
||||
return (SuggestionsSupplier<S>) 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<List<Character>> CONFUSABLE_CHARACTERS = ImmutableList.of(
|
||||
ImmutableList.of('o', '0'),
|
||||
ImmutableList.of('i', '1', 'l'),
|
||||
ImmutableList.of('b', '8')
|
||||
);
|
||||
private static ToIntBiFunction<Character, Character> CHAR_DISTANCE = (c1, c2) -> {
|
||||
if (c1.equals(c2))
|
||||
return 0;
|
||||
for (List<Character> charTab : CONFUSABLE_CHARACTERS) {
|
||||
if (charTab.contains(c1) && charTab.contains(c2))
|
||||
return CLOSE_CHAR_DISTANCE;
|
||||
}
|
||||
return DIFF_CHAR_DISTANCE;
|
||||
};
|
||||
|
||||
private static LoadingCache<String, List<Pair<String, UUID>>> namesCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(2, TimeUnit.MINUTES)
|
||||
.maximumSize(1)
|
||||
.build(CacheLoader.from((String k) -> {
|
||||
List<Pair<String, UUID>> 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<String, SearchResponse> searchCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(2, TimeUnit.MINUTES)
|
||||
.maximumSize(100)
|
||||
.build(CacheLoader.from((String query) -> {
|
||||
List<FoundName> 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<UUID, SearchResponseProfile> 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<SearchResponseProfile> 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<SearchResponseProfile> {
|
||||
public int d;
|
||||
public String id;
|
||||
public String name;
|
||||
public String displayName;
|
||||
public List<FoundName> 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<SearchResponseProfile> profiles;
|
||||
private SearchResponse(List<SearchResponseProfile> p) {
|
||||
profiles = p;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -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<SQLPlayer> {
|
||||
|
||||
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<SQLPlayer, UUID> playerId = field(CHAR36_UUID, false);
|
||||
public static final SQLField<SQLPlayer, String> playerName = field(VARCHAR(16), false);
|
||||
public static final SQLField<SQLPlayer, UUID> token = field(CHAR36_UUID, true);
|
||||
public static final SQLField<SQLPlayer, String> mailCheck = field(VARCHAR(255), true);
|
||||
public static final SQLField<SQLPlayer, String> password = field(VARCHAR(255), true);
|
||||
public static final SQLField<SQLPlayer, String> mail = field(VARCHAR(255), true);
|
||||
public static final SQLField<SQLPlayer, String> playerDisplayName = field(VARCHAR(255),
|
||||
false);
|
||||
public static final SQLField<SQLPlayer, Long> firstTimeInGame = field(BIGINT, false, 0L);
|
||||
public static final SQLField<SQLPlayer, Long> timeWebRegister = field(BIGINT, true);
|
||||
public static final SQLField<SQLPlayer, Long> lastTimeInGame = field(BIGINT, true);
|
||||
public static final SQLField<SQLPlayer, Long> lastWebActivity = field(BIGINT, false, 0L);
|
||||
public static final SQLField<SQLPlayer, String> onlineInServer = field(VARCHAR(32), true);
|
||||
public static final SQLField<SQLPlayer, String> skinURL = field(VARCHAR(255), true);
|
||||
public static final SQLField<SQLPlayer, Boolean> isVanish = field(BOOLEAN, false,
|
||||
(Boolean) false);
|
||||
public static final SQLField<SQLPlayer, Date> birthday = field(DATE, true);
|
||||
public static final SQLField<SQLPlayer, Integer> lastYearCelebratedBirthday = field(INT,
|
||||
false, 0);
|
||||
public static final SQLField<SQLPlayer, Long> banTimeout = field(BIGINT, true);
|
||||
public static final SQLField<SQLPlayer, Long> muteTimeout = field(BIGINT, true);
|
||||
public static final SQLField<SQLPlayer, Boolean> isWhitelisted = field(BOOLEAN, false,
|
||||
(Boolean) false);
|
||||
public static final SQLField<SQLPlayer, Long> bambou = field(BIGINT, false, 0L);
|
||||
public static final SQLField<SQLPlayer, String> 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<SQLPlayer> getPlayersFromUUIDs(Set<UUID> playerIds) throws DBException {
|
||||
|
||||
if (playerIds == null || playerIds.isEmpty()) {
|
||||
return new SQLElementList<>();
|
||||
}
|
||||
|
||||
return DB.getAll(SQLPlayer.class, playerId.in(playerIds));
|
||||
|
||||
}
|
||||
}
|
@ -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<SQLPlayerIgnore> {
|
||||
|
||||
public SQLPlayerIgnore() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SQLPlayerIgnore(int id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String tableName() {
|
||||
return "pandacube_player_ignore";
|
||||
}
|
||||
|
||||
public static final SQLFKField<SQLPlayerIgnore, UUID, SQLPlayer> ignorer = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId);
|
||||
public static final SQLFKField<SQLPlayerIgnore, UUID, SQLPlayer> 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<UUID, SQLPlayer> getIgnoredPlayer(UUID ignorer) throws DBException {
|
||||
return DB.getAll(SQLPlayerIgnore.class, SQLPlayerIgnore.ignorer.eq(ignorer))
|
||||
.getReferencedEntriesInGroups(SQLPlayerIgnore.ignored);
|
||||
}
|
||||
|
||||
}
|
@ -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<SQLPlayerNameHistory> {
|
||||
|
||||
public SQLPlayerNameHistory() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SQLPlayerNameHistory(int id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String tableName() {
|
||||
return "pandacube_player_name_history";
|
||||
}
|
||||
|
||||
public static final SQLFKField<SQLPlayerNameHistory, UUID, SQLPlayer> playerId = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId);
|
||||
public static final SQLField<SQLPlayerNameHistory, String> playerName = field(VARCHAR(16), false);
|
||||
public static final SQLField<SQLPlayerNameHistory, Long> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.search;
|
||||
package fr.pandacube.lib.core.search;
|
||||
|
||||
import java.util.Set;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Callback<T> {
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.measurement;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Arrays;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
public class EnumUtil {
|
||||
|
32
Core/src/main/java/fr/pandacube/lib/core/util/FileUtils.java
Normal file
32
Core/src/main/java/fr/pandacube/lib/core/util/FileUtils.java
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
//******************************************************************************
|
||||
//***
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.ToIntBiFunction;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.List;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.measurement;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
@ -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";
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.UUID;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.util.measurement;
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
public class Tick {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
public class ORMInitTableException extends ORMException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/* package */ <E extends SQLElement<E>> ORMInitTableException(Class<E> tableElem) {
|
||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"));
|
||||
}
|
||||
|
||||
/* package */ <E extends SQLElement<E>> ORMInitTableException(Class<E> tableElem, Throwable t) {
|
||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"), t);
|
||||
}
|
||||
|
||||
}
|
@ -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<E extends SQLElement<E>> {
|
||||
|
||||
public abstract Pair<String, List<Object>> 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<E> and(SQLWhere<E> other) {
|
||||
return new SQLWhereAnd<E>().and(this).and(other);
|
||||
}
|
||||
|
||||
public SQLWhereOr<E> or(SQLWhere<E> other) {
|
||||
return new SQLWhereOr<E>().or(this).or(other);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLWhereAnd<E> and() {
|
||||
return new SQLWhereAnd<>();
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLWhereOr<E> or() {
|
||||
return new SQLWhereOr<>();
|
||||
}
|
||||
|
||||
public static String escapeLike(String str) {
|
||||
return str.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%");
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
public class SQLWhereAnd<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
||||
|
||||
/* package */ SQLWhereAnd() {
|
||||
super(SQLBoolOp.AND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLWhereAnd<E> and(SQLWhere<E> other) {
|
||||
add(other);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@ -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<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLBoolOp operator;
|
||||
protected List<SQLWhere<E>> conditions = new ArrayList<>();
|
||||
|
||||
/* package */ SQLWhereChain(SQLBoolOp op) {
|
||||
if (op == null) throw new IllegalArgumentException("op can't be null");
|
||||
operator = op;
|
||||
}
|
||||
|
||||
protected void add(SQLWhere<E> sqlWhere) {
|
||||
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
|
||||
conditions.add(sqlWhere);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() throws ORMException {
|
||||
if (conditions.isEmpty()) {
|
||||
throw new ORMException("SQLWhereChain needs at least one element inside !");
|
||||
}
|
||||
|
||||
String sql = "";
|
||||
List<Object> params = new ArrayList<>();
|
||||
boolean first = true;
|
||||
|
||||
for (SQLWhere<E> w : conditions) {
|
||||
if (!first) sql += " " + operator.sql + " ";
|
||||
first = false;
|
||||
|
||||
Pair<String, List<Object>> ret = w.toSQL();
|
||||
sql += "(" + ret.getValue0() + ")";
|
||||
params.addAll(ret.getValue1());
|
||||
}
|
||||
|
||||
return new Pair<>(sql, params);
|
||||
}
|
||||
|
||||
/* package */ enum SQLBoolOp {
|
||||
/** Equivalent to SQL "<code>AND</code>" */
|
||||
AND("AND"),
|
||||
/** Equivalent to SQL "<code>OR</code>" */
|
||||
OR("OR");
|
||||
/* package */ final String sql;
|
||||
|
||||
private SQLBoolOp(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
/* package */ class SQLWhereComp<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> 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 */ <T> SQLWhereComp(SQLField<E, T> 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<String, List<Object>> toSQL() throws ORMException {
|
||||
List<Object> params = new ArrayList<>();
|
||||
SQLElement.addValueToSQLObjectList(params, left, right);
|
||||
return new Pair<>("`" + left.getName() + "` " + comp.sql + " ? ", params);
|
||||
}
|
||||
|
||||
/* package */ enum SQLComparator {
|
||||
/** Equivalent to SQL "<code>=</code>" */
|
||||
EQ("="),
|
||||
/** Equivalent to SQL "<code>></code>" */
|
||||
GT(">"),
|
||||
/** Equivalent to SQL "<code>>=</code>" */
|
||||
GEQ(">="),
|
||||
/** Equivalent to SQL "<code><</code>" */
|
||||
LT("<"),
|
||||
/** Equivalent to SQL "<code><=</code>" */
|
||||
LEQ("<="),
|
||||
/** Equivalent to SQL "<code>!=</code>" */
|
||||
NEQ("!=");
|
||||
|
||||
/* package */ final String sql;
|
||||
|
||||
private SQLComparator(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> field;
|
||||
private Collection<?> values;
|
||||
|
||||
/* package */ <T> SQLWhereIn(SQLField<E, T> f, Collection<T> 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<String, List<Object>> toSQL() throws ORMException {
|
||||
List<Object> 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);
|
||||
}
|
||||
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
/* package */ class SQLWhereLike<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> 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<E, ?> 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<String, List<Object>> toSQL() {
|
||||
ArrayList<Object> params = new ArrayList<>();
|
||||
params.add(likeExpr);
|
||||
return new Pair<>("`" + field.getName() + "` LIKE ? ", params);
|
||||
}
|
||||
|
||||
}
|
@ -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<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> 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<E, ?> 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<String, List<Object>> toSQL() {
|
||||
return new Pair<>("`" + fild.getName() + "` IS " + ((nulll) ? "NULL" : "NOT NULL"), new ArrayList<>());
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
public class SQLWhereOr<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
||||
|
||||
/* package */ SQLWhereOr() {
|
||||
super(SQLBoolOp.OR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLWhereOr<E> or(SQLWhere<E> other) {
|
||||
add(other);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
20
Paper/.classpath
Normal file
20
Paper/.classpath
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
1
Paper/.gitignore
vendored
Normal file
1
Paper/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target/
|
23
Paper/.project
Normal file
23
Paper/.project
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>pandalib-paper</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user