Refactoring

This commit is contained in:
Marc Baloup 2021-03-21 20:17:31 +01:00
parent 87f2bf0b19
commit 5019788865
Signed by: marcbal
GPG Key ID: BBC0FE3ABC30B893
122 changed files with 6119 additions and 663 deletions

17
.project Normal file
View 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>

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View File

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@ -1,6 +1,6 @@
eclipse.preferences.version=1 eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 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.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore

View File

@ -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;
}
}

View File

@ -1,9 +1,9 @@
package fr.pandacube.util.text_display; package fr.pandacube.lib.core.chat;
import java.awt.Color; import java.awt.Color;
import java.util.UUID; 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.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ClickEvent;
@ -118,7 +118,7 @@ public abstract class Chat extends ChatStatic {
* @return this, for method chaining * @return this, for method chaining
*/ */
public Chat thenEmptyCharLine() { 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 * @return this, for method chaining
*/ */
public Chat thenLeftTextCharLine(Chat leftText) { public Chat thenLeftTextCharLine(Chat leftText) {
return then(ChatUtil.leftText(chat().decorationColor().thenText(" ").then(leftText).thenText(" ").get(), Pandacube.CHAT_DECORATION_CHAR, return then(ChatUtil.leftText(chat().decorationColor().thenText(" ").then(leftText).thenText(" ").get(), config.decorationChar,
Pandacube.CHAT_DECORATION_COLOR, Pandacube.CHAT_NB_CHAR_MARGIN, console)); config.decorationColor, config.nbCharMargin, console));
} }
/** /**
* Draws a full line with the default decoration char, colored with the default decoration color, * 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 * @return this, for method chaining
*/ */
public Chat thenRightTextCharLine(Chat rightText) { public Chat thenRightTextCharLine(Chat rightText) {
return then(ChatUtil.rightText(chat().decorationColor().thenText(" ").then(rightText).thenText(" ").get(), Pandacube.CHAT_DECORATION_CHAR, return then(ChatUtil.rightText(chat().decorationColor().thenText(" ").then(rightText).thenText(" ").get(), config.decorationChar,
Pandacube.CHAT_DECORATION_COLOR, Pandacube.CHAT_NB_CHAR_MARGIN, console)); config.decorationColor, config.nbCharMargin, console));
} }
/** /**
* Draws a full line with the default decoration char, colored with the default decoration color, * 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 * @return this, for method chaining
*/ */
public Chat thenCenterTextCharLine(Chat centerText) { public Chat thenCenterTextCharLine(Chat centerText) {
return then(ChatUtil.centerText(chat().decorationColor().thenText(" ").then(centerText).thenText(" ").get(), Pandacube.CHAT_DECORATION_CHAR, return then(ChatUtil.centerText(chat().decorationColor().thenText(" ").then(centerText).thenText(" ").get(), config.decorationChar,
Pandacube.CHAT_DECORATION_COLOR, console)); config.decorationColor, console));
} }
/** /**
* Draws a full line with the default decoration char, colored with the default decoration color, * 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 yellow() { return color(ChatColor.YELLOW); }
public FormatableChat white() { return color(ChatColor.WHITE); } public FormatableChat white() { return color(ChatColor.WHITE); }
public FormatableChat successColor() { return color(Pandacube.CHAT_SUCCESS_COLOR); } public FormatableChat successColor() { return color(config.successColor); }
public FormatableChat failureColor() { return color(Pandacube.CHAT_FAILURE_COLOR); } public FormatableChat failureColor() { return color(config.failureColor); }
public FormatableChat infoColor() { return color(Pandacube.CHAT_INFO_COLOR); } public FormatableChat infoColor() { return color(config.infoColor); }
public FormatableChat dataColor() { return color(Pandacube.CHAT_DATA_COLOR); } public FormatableChat dataColor() { return color(config.dataColor); }
public FormatableChat decorationColor() { return color(Pandacube.CHAT_DECORATION_COLOR); } public FormatableChat decorationColor() { return color(config.decorationColor); }
public FormatableChat font(String f) { component.setFont(f); return this; } public FormatableChat font(String f) { component.setFont(f); return this; }
@ -306,4 +306,33 @@ public abstract class Chat extends ChatStatic {
return values; 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;
}
} }

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.text_display; package fr.pandacube.lib.core.chat;
import java.awt.Color; import java.awt.Color;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -1,8 +1,8 @@
package fr.pandacube.util.text_display; package fr.pandacube.lib.core.chat;
import java.util.Objects; 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.BaseComponent;
import net.md_5.bungee.api.chat.KeybindComponent; import net.md_5.bungee.api.chat.KeybindComponent;
import net.md_5.bungee.api.chat.Keybinds; import net.md_5.bungee.api.chat.Keybinds;

View File

@ -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.lib.core.chat.ChatStatic.chat;
import static fr.pandacube.util.text_display.ChatStatic.chat; import static fr.pandacube.lib.core.chat.ChatStatic.chatComponent;
import static fr.pandacube.util.text_display.ChatStatic.legacyText; import static fr.pandacube.lib.core.chat.ChatStatic.legacyText;
import static fr.pandacube.util.text_display.ChatStatic.text; import static fr.pandacube.lib.core.chat.ChatStatic.text;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -14,8 +14,7 @@ import java.util.TreeSet;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import fr.pandacube.Pandacube; import fr.pandacube.lib.core.chat.Chat.FormatableChat;
import fr.pandacube.util.text_display.Chat.FormatableChat;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent; 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; String dispURL = (url.length() > 50) ? (url.substring(0, 48) + "...") : url;
return chat() return chat()
.clickURL(url) .clickURL(url)
.color(Pandacube.CHAT_URL_COLOR) .color(Chat.getConfig().urlColor)
.hoverText( .hoverText(
hover != null ? hover : Chat.text(dispURL) hover != null ? hover : Chat.text(dispURL)
) )
@ -85,7 +84,7 @@ public class ChatUtil {
/* package */ static BaseComponent createCommandLink(Chat d, String commandWithSlash, Chat hoverText) { /* package */ static BaseComponent createCommandLink(Chat d, String commandWithSlash, Chat hoverText) {
FormatableChat c = chat() FormatableChat c = chat()
.clickCommand(commandWithSlash) .clickCommand(commandWithSlash)
.color(Pandacube.CHAT_COMMAND_COLOR); .color(Chat.getConfig().commandColor);
if (hoverText != null) if (hoverText != null)
c.hoverText(hoverText); c.hoverText(hoverText);
return c.then(d).get(); return c.then(d).get();
@ -110,7 +109,7 @@ public class ChatUtil {
/* package */ static BaseComponent createCommandSuggest(Chat d, String commandWithSlash, Chat hoverText) { /* package */ static BaseComponent createCommandSuggest(Chat d, String commandWithSlash, Chat hoverText) {
FormatableChat c = chat() FormatableChat c = chat()
.clickSuggest(commandWithSlash) .clickSuggest(commandWithSlash)
.color(Pandacube.CHAT_COMMAND_COLOR); .color(Chat.getConfig().commandColor);
if (hoverText != null) if (hoverText != null)
c.hoverText(hoverText); c.hoverText(hoverText);
return c.then(d).get(); 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)); FormatableChat pDisp = chatComponent(createCommandLink(Integer.toString(page), String.format(cmdFormat, page), "Aller à la page " + page));
if (page == currentPage) { if (page == currentPage) {
pDisp.color(Pandacube.CHAT_COMMAND_HIGHLIGHTED_COLOR); pDisp.color(Chat.getConfig().highlightedCommandColor);
} }
d.then(pDisp); d.then(pDisp);

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.text_display; package fr.pandacube.lib.core.chat;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;

View File

@ -1,8 +1,8 @@
package fr.pandacube.util.commands; package fr.pandacube.lib.core.commands;
import java.util.Arrays; import java.util.Arrays;
import fr.pandacube.util.text_display.ChatStatic; import fr.pandacube.lib.core.chat.ChatStatic;
public class AbstractCommand extends ChatStatic { public class AbstractCommand extends ChatStatic {

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.commands; package fr.pandacube.lib.core.commands;
import java.util.logging.Logger; import java.util.logging.Logger;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.commands; package fr.pandacube.lib.core.commands;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -10,7 +10,7 @@ import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import fr.pandacube.util.ListUtil; import fr.pandacube.lib.core.util.ListUtil;
@FunctionalInterface @FunctionalInterface
public interface SuggestionsSupplier<S> { public interface SuggestionsSupplier<S> {

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.config; package fr.pandacube.lib.core.config;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@ -9,8 +9,8 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import fr.pandacube.util.Log; import fr.pandacube.lib.core.chat.ChatColorUtil;
import fr.pandacube.util.text_display.ChatColorUtil; import fr.pandacube.lib.core.util.Log;
/** /**
* Classe chargeant en mémoire un fichier de configuration ou un dossier donné * Classe chargeant en mémoire un fichier de configuration ou un dossier donné
* @author Marc Baloup * @author Marc Baloup

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.config; package fr.pandacube.lib.core.config;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.orm; package fr.pandacube.lib.core.db;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
@ -10,54 +10,61 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.javatuples.Pair; 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 * @author Marc Baloup
*
*/ */
public final class ORM { public final class DB {
private static List<Class<? extends SQLElement<?>>> tables = new ArrayList<>(); private static List<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
private static Map<Class<? extends SQLElement<?>>, String> tableNames = new HashMap<>(); private static Map<Class<? extends SQLElement<?>>, String> tableNames = new HashMap<>();
private static DBConnection connection; private static DBConnection connection;
/* package */ static String tablePrefix = "";
public static DBConnection getConnection() { public static DBConnection getConnection() {
return connection; 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; 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; if (tables.contains(elemClass)) return;
try { try {
tables.add(elemClass); 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(); E instance = elemClass.getConstructor().newInstance();
String tableName = instance.tableName(); String tableName = tablePrefix + instance.tableName();
tableNames.put(elemClass, tableName); tableNames.put(elemClass, tableName);
if (!tableExistInDB(tableName)) createTable(instance); 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) { } 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 { private static <E extends SQLElement<E>> void createTable(E elem) throws SQLException {
String tableName = tablePrefix + elem.tableName();
String sql = "CREATE TABLE IF NOT EXISTS " + elem.tableName() + " ("; String sql = "CREATE TABLE IF NOT EXISTS " + tableName + " (";
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
Collection<SQLField<E, ?>> tableFields = elem.getFields().values(); 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); initTable(elemClass);
return tableNames.get(elemClass); return tableNames.get(elemClass);
} }
@ -97,91 +104,91 @@ public final class ORM {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <E extends SQLElement<E>> SQLField<E, Integer> getSQLIdField(Class<E> elemClass) public static <E extends SQLElement<E>> SQLField<E, Integer> getSQLIdField(Class<E> elemClass)
throws ORMInitTableException { throws DBInitTableException {
initTable(elemClass); initTable(elemClass);
return (SQLField<E, Integer>) SQLElement.fieldsCache.get(elemClass).get("id"); 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)); return getByIds(elemClass, Arrays.asList(ids));
} }
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Collection<Integer> 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); 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)); return getFirst(elemClass, getSQLIdField(elemClass).eq(id));
} }
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where) public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where)
throws ORMException { throws DBException {
return getFirst(elemClass, where, null, null); return getFirst(elemClass, where, null, null);
} }
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLOrderBy<E> orderBy) public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLOrderBy<E> orderBy)
throws ORMException { throws DBException {
return getFirst(elemClass, null, orderBy, null); return getFirst(elemClass, null, orderBy, null);
} }
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy) 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); 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) 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); SQLElementList<E> elts = getAll(elemClass, where, orderBy, 1, offset);
return (elts.size() == 0) ? null : elts.get(0); 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); 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); return getAll(elemClass, where, null, null, null);
} }
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where, 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); return getAll(elemClass, where, orderBy, null, null);
} }
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where, 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); return getAll(elemClass, where, orderBy, limit, null);
} }
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where, 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<>(); SQLElementList<E> elmts = new SQLElementList<>();
forEach(elemClass, where, orderBy, limit, offset, elmts::add); forEach(elemClass, where, orderBy, limit, offset, elmts::add);
return elmts; 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); forEach(elemClass, null, null, null, null, action);
} }
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where, 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); forEach(elemClass, where, null, null, null, action);
} }
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where, 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); forEach(elemClass, where, orderBy, null, null, action);
} }
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where, 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); forEach(elemClass, where, orderBy, limit, null, action);
} }
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where, 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); initTable(elemClass);
try { try {
@ -206,18 +213,18 @@ public final class ORM {
} }
} }
} catch (SQLException e) { } 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); 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); initTable(elemClass);
try { try {
@ -238,17 +245,17 @@ public final class ORM {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
throw new ORMException(e); throw new DBException(e);
} }
throw new ORMException("Cant retrieve element count from database (The ResultSet may be empty)"); throw new DBException("Cant 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 { try {
PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql); PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql);
int i = 1; int i = 1;
@ -264,7 +271,7 @@ public final class ORM {
return rs; return rs;
} catch (SQLException e) { } 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); 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(); return new SQLUpdate<>(elemClass, where, values).execute();
} }
@ -286,9 +293,9 @@ public final class ORM {
* @param elemClass the SQLElement representing the table. * @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)}. * @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}. * @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); initTable(elemClass);
if (where == null) { 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)) { try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql)) {
int i = 1; int i = 1;
@ -320,22 +327,22 @@ public final class ORM {
return ps.executeUpdate(); return ps.executeUpdate();
} catch (SQLException e) { } 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()) { try (Statement stmt = connection.getNativeConnection().createStatement()) {
return stmt.executeUpdate("TRUNCATE `" + getTableName(elemClass) + "`"); return stmt.executeUpdate("TRUNCATE `" + getTableName(elemClass) + "`");
} catch(SQLException e) { } catch(SQLException e) {
throw new ORMException(e); throw new DBException(e);
} }
} }
@SuppressWarnings("unchecked") @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 { try {
E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id")); E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id"));
@ -363,7 +370,7 @@ public final class ORM {
try { try {
val = ((SQLCustomType<Object, Object>)sqlField.type).dbToJavaConv.apply(val); val = ((SQLCustomType<Object, Object>)sqlField.type).dbToJavaConv.apply(val);
} catch (Exception e) { } 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); +"(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()); "This SQLElement representing a database entry is not valid for save : " + instance.toString());
return instance; return instance;
} catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | SQLException e) { } 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() {}
} }

View File

@ -1,11 +1,11 @@
package fr.pandacube.util.orm; package fr.pandacube.lib.core.db;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import fr.pandacube.util.Log; import fr.pandacube.lib.core.util.Log;
public class DBConnection { public class DBConnection {
private static final long CONNECTION_CHECK_TIMEOUT = 30000; // in ms private static final long CONNECTION_CHECK_TIMEOUT = 30000; // in ms

View 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);
}
}

View File

@ -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);
}
}

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.orm; package fr.pandacube.lib.core.db;
import java.util.function.Function; import java.util.function.Function;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.orm; package fr.pandacube.lib.core.db;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.sql.Date; import java.sql.Date;
@ -22,14 +22,14 @@ import org.apache.commons.lang.builder.ToStringBuilder;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import fr.pandacube.util.EnumUtil; import fr.pandacube.lib.core.util.EnumUtil;
import fr.pandacube.util.Log; import fr.pandacube.lib.core.util.Log;
public abstract class SQLElement<E extends SQLElement<E>> { public abstract class SQLElement<E extends SQLElement<E>> {
/** cache for fields for each subclass of SQLElement */ /** cache for fields for each subclass of SQLElement */
/* package */ static final Map<Class<? extends SQLElement<?>>, SQLFieldMap<? extends SQLElement<?>>> fieldsCache = new HashMap<>(); /* 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 boolean stored = false;
private int id; private int id;
@ -43,8 +43,8 @@ public abstract class SQLElement<E extends SQLElement<E>> {
public SQLElement() { public SQLElement() {
try { try {
ORM.initTable((Class<E>)getClass()); DB.initTable((Class<E>)getClass());
} catch (ORMInitTableException e) { } catch (DBInitTableException e) {
throw new RuntimeException(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(); 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 * @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. * @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); T fkValue = get(field);
if (fkValue == null) return null; 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. * @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. * @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()); T value = get(field.getPrimaryField());
if (value == null) return new SQLElementList<>(); 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() { public boolean isValidForSave() {
@ -225,11 +225,11 @@ public abstract class SQLElement<E extends SQLElement<E>> {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public E save() throws ORMException { public E save() throws DBException {
if (!isValidForSave()) if (!isValidForSave())
throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved."); 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 { try {
if (stored) { // mettre à jour les valeurs dans la base 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; 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 else { // ajouter dans la base
@ -268,7 +268,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
} }
try (PreparedStatement ps = db.getNativeConnection().prepareStatement( 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)) { Statement.RETURN_GENERATED_KEYS)) {
int i = 1; int i = 1;
@ -287,19 +287,19 @@ public abstract class SQLElement<E extends SQLElement<E>> {
modifiedSinceLastSave.clear(); modifiedSinceLastSave.clear();
} catch (SQLException e) { } catch (SQLException e) {
throw new ORMException("Error while saving data", e); throw new DBException("Error while saving data", e);
} }
return (E) this; return (E) this;
} }
@SuppressWarnings({ "rawtypes", "unchecked" }) @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) { if (jValue != null && field.type instanceof SQLCustomType) {
try { try {
jValue = ((SQLCustomType)field.type).javaToDbConv.apply(jValue); jValue = ((SQLCustomType)field.type).javaToDbConv.apply(jValue);
} catch (Exception e) { } 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); +"(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"); 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 if (stored) { // supprimer la ligne de la base
try (PreparedStatement st = db.getNativeConnection() 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()); Log.debug(st.toString());
st.executeUpdate(); st.executeUpdate();
markAsNotStored(); markAsNotStored();
} catch (SQLException e) { } catch (SQLException e) {
throw new ORMException(e); throw new DBException(e);
} }
} }

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.orm; package fr.pandacube.lib.core.db;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
@ -12,7 +12,7 @@ import java.util.stream.Collectors;
import com.google.gson.JsonArray; 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 * @throws SQLException
*/ */
public synchronized int saveCommon() throws ORMException { public synchronized int saveCommon() throws DBException {
List<E> storedEl = getStoredEl(); List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return 0; if (storedEl.isEmpty()) return 0;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Class<E> classEl = (Class<E>)storedEl.get(0).getClass(); 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()) storedEl.get(0).getFieldId().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList())
), ),
modifiedValues); 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. * except if you really want to fetch the data before removing them from database.
*/ */
@Deprecated @Deprecated
@ -118,12 +118,12 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Class<E> classEl = (Class<E>)storedEl.get(0).getClass(); 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())) storedEl.get(0).getFieldId().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList()))
); );
for (E el : storedEl) for (E el : storedEl)
el.markAsNotStored(); el.markAsNotStored();
} catch (ORMException e) { } catch (DBException e) {
Log.severe(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<>(); Set<T> values = new HashSet<>();
forEach(v -> { forEach(v -> {
T val = v.get(foreignKey); T val = v.get(foreignKey);
@ -143,12 +143,12 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
return new SQLElementList<>(); 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); SQLElementList<P> foreignElemts = getReferencedEntries(foreignKey, null);
Map<T, P> ret = new HashMap<>(); 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<>(); Set<T> values = new HashSet<>();
forEach(v -> { forEach(v -> {
T val = v.get(foreignKey.getPrimaryField()); T val = v.get(foreignKey.getPrimaryField());
@ -170,12 +170,12 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
return new SQLElementList<>(); 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); SQLElementList<F> foreignElements = getReferencingForeignEntries(foreignKey, orderBy, limit, offset);
Map<T, SQLElementList<F>> map = new HashMap<>(); Map<T, SQLElementList<F>> map = new HashMap<>();

View File

@ -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) { /* 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"); if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
try { try {
SQLField<F, Integer> f = ORM.getSQLIdField(fkEl); SQLField<F, Integer> f = DB.getSQLIdField(fkEl);
return new SQLFKField<>(f.type, nul, deflt, fkEl, f); 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); Log.severe("Can't create Foreign key Field targetting id field of '"+fkEl+"'", e);
return null; 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) { private void construct(Class<P> fkEl, SQLField<P, T> fkF) {
if (fkF == null) throw new IllegalArgumentException("foreignKeyField can't be null"); if (fkF == null) throw new IllegalArgumentException("foreignKeyField can't be null");
try { try {
ORM.initTable(fkEl); DB.initTable(fkEl);
} catch (ORMInitTableException e) { } catch (DBInitTableException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.orm; package fr.pandacube.lib.core.db;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -6,7 +6,11 @@ import java.util.List;
import org.javatuples.Pair; 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> { public class SQLField<E extends SQLElement<E>, T> {

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.orm; package fr.pandacube.lib.core.db;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.orm; package fr.pandacube.lib.core.db;
public class SQLType<T> { public class SQLType<T> {

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.orm; package fr.pandacube.lib.core.db;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -7,7 +7,7 @@ import java.util.Map;
import org.javatuples.Pair; import org.javatuples.Pair;
import fr.pandacube.util.Log; import fr.pandacube.lib.core.util.Log;
public class SQLUpdate<E extends SQLElement<E>> { public class SQLUpdate<E extends SQLElement<E>> {
@ -37,14 +37,14 @@ public class SQLUpdate<E extends SQLElement<E>> {
return this; return this;
} }
public int execute() throws ORMException { public int execute() throws DBException {
if (values.isEmpty()) { 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; return 0;
} }
String sql = "UPDATE " + ORM.getTableName(elemClass) + " SET "; String sql = "UPDATE " + DB.getTableName(elemClass) + " SET ";
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
boolean first = true; boolean first = true;
@ -64,7 +64,7 @@ public class SQLUpdate<E extends SQLElement<E>> {
sql += ";"; sql += ";";
return ORM.customUpdateStatement(sql, params); return DB.customUpdateStatement(sql, params);
} }
} }

View 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>&lt;</code>" */
LT("<"),
/** Equivalent to SQL "<code>&lt;=</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<>());
}
}
}

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.net; package fr.pandacube.lib.core.net;
import java.util.Arrays; import java.util.Arrays;

View File

@ -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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import fr.pandacube.Pandacube;
public final class ByteBuffer implements Cloneable { public final class ByteBuffer implements Cloneable {
public static final Charset NETWORK_CHARSET = Charset.forName("UTF-8");
private java.nio.ByteBuffer buff; private java.nio.ByteBuffer buff;
public ByteBuffer() { public ByteBuffer() {
@ -223,7 +224,7 @@ public final class ByteBuffer implements Cloneable {
if (s == null) { if (s == null) {
return putInt(-1); 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() { public String getString() {
byte[] binaryString = getSizedByteArray(); byte[] binaryString = getSizedByteArray();
return (binaryString == null) ? null : new String(binaryString, Pandacube.NETWORK_CHARSET); return (binaryString == null) ? null : new String(binaryString, NETWORK_CHARSET);
} }
/** /**

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.net; package fr.pandacube.lib.core.net;
import java.util.Arrays; import java.util.Arrays;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.net; package fr.pandacube.lib.core.net;
import java.util.Arrays; import java.util.Arrays;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.net; package fr.pandacube.lib.core.net;
@FunctionalInterface @FunctionalInterface
public interface PPacketListener<P extends PPacket> { public interface PPacketListener<P extends PPacket> {

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.net; package fr.pandacube.lib.core.net;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
@ -14,8 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringBuilder;
import fr.pandacube.Pandacube; import fr.pandacube.lib.core.util.Log;
import fr.pandacube.util.Log;
public class PServer extends Thread implements Closeable { public class PServer extends Thread implements Closeable {
private static AtomicInteger connectionCounterId = new AtomicInteger(0); private static AtomicInteger connectionCounterId = new AtomicInteger(0);
@ -51,14 +50,14 @@ public class PServer extends Thread implements Closeable {
try { try {
socket = new ServerSocket(); socket = new ServerSocket();
socket.setReceiveBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE); socket.setReceiveBufferSize(PSocket.NETWORK_TCP_BUFFER_SIZE);
socket.setPerformancePreferences(0, 1, 0); socket.setPerformancePreferences(0, 1, 0);
socket.bind(new InetSocketAddress(port)); socket.bind(new InetSocketAddress(port));
while (true) { while (true) {
Socket socketClient = socket.accept(); Socket socketClient = socket.accept();
socketClient.setSendBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE); socketClient.setSendBufferSize(PSocket.NETWORK_TCP_BUFFER_SIZE);
socketClient.setSoTimeout(Pandacube.NETWORK_TIMEOUT); socketClient.setSoTimeout(PSocket.NETWORK_TIMEOUT);
try { try {
@SuppressWarnings("resource") @SuppressWarnings("resource")

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.net; package fr.pandacube.lib.core.net;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.Closeable; import java.io.Closeable;
@ -19,8 +19,7 @@ import org.apache.commons.lang.builder.ToStringBuilder;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import fr.pandacube.Pandacube; import fr.pandacube.lib.core.util.Log;
import fr.pandacube.util.Log;
/** /**
* A wrapper for a {@link Socket}. The connection must point to a software using {@link PServer} * A wrapper for a {@link Socket}. The connection must point to a software using {@link PServer}
@ -35,6 +34,10 @@ import fr.pandacube.util.Log;
* *
*/ */
public class PSocket extends Thread implements Closeable { public 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 boolean server = false;
private Socket socket; private Socket socket;
@ -80,8 +83,8 @@ public class PSocket extends Thread implements Closeable {
try { try {
if (socket == null) { if (socket == null) {
socket = new Socket(); socket = new Socket();
socket.setReceiveBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE); socket.setReceiveBufferSize(NETWORK_TCP_BUFFER_SIZE);
socket.setSendBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE); socket.setSendBufferSize(NETWORK_TCP_BUFFER_SIZE);
socket.setSoTimeout(10000); // initial timeout before login socket.setSoTimeout(10000); // initial timeout before login
@ -137,7 +140,7 @@ public class PSocket extends Thread implements Closeable {
password = null; password = null;
} }
socket.setSoTimeout(Pandacube.NETWORK_TIMEOUT); socket.setSoTimeout(NETWORK_TIMEOUT);
Log.info(getName() + " connected."); Log.info(getName() + " connected.");
@ -202,7 +205,7 @@ public class PSocket extends Thread implements Closeable {
byte[] nBytes = new byte[nSize]; byte[] nBytes = new byte[nSize];
in.readFully(nBytes); in.readFully(nBytes);
String name = new String(nBytes, Pandacube.NETWORK_CHARSET); String name = new String(nBytes, ByteBuffer.NETWORK_CHARSET);
int packetId = in.readInt(); int packetId = in.readInt();
@ -241,7 +244,7 @@ public class PSocket extends Thread implements Closeable {
if (packet.content == null) if (packet.content == null)
throw new IllegalArgumentException("packet.content can't be 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) if (nameBytes.length > 127)
throw new IllegalArgumentException("packet.name must take fewer than 128 bytes when converted to UTF-8"); throw new IllegalArgumentException("packet.name must take fewer than 128 bytes when converted to UTF-8");
byte nameSize = (byte)nameBytes.length; byte nameSize = (byte)nameBytes.length;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.net; package fr.pandacube.lib.core.net;
public interface PSocketConnectionListener { public interface PSocketConnectionListener {

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.network_api.client; package fr.pandacube.lib.core.network_api.client;
import java.io.PrintStream; import java.io.PrintStream;

View File

@ -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.IOException;
import java.io.PrintStream; import java.io.PrintStream;

View File

@ -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.BufferedReader;
import java.io.IOException; import java.io.IOException;

View File

@ -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.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import fr.pandacube.util.Log; import fr.pandacube.lib.core.util.Log;
@Deprecated @Deprecated
public abstract class AbstractRequestExecutor { public abstract class AbstractRequestExecutor {

View File

@ -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.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;

View File

@ -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.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.net.Socket; import java.net.Socket;
import fr.pandacube.util.Log; import fr.pandacube.lib.core.network_api.server.RequestAnalyser.BadRequestException;
import fr.pandacube.util.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 * Prends en charge un socket client et le transmet au gestionnaire de paquet

View File

@ -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.BufferedReader;
import java.io.IOException; import java.io.IOException;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.network_api.server; package fr.pandacube.lib.core.network_api.server;
import java.io.PrintStream; import java.io.PrintStream;

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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("Cant 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();
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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 cant be null");
Preconditions.checkNotNull(type, "type cant 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 cant be null");
Preconditions.checkNotNull(type, "type cant be null");
Preconditions.checkNotNull(permission, "permission cant 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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View 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 permissions prefix(es) and suffix(es) of the player,
* and with color codes translated to Minecrafts 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;
}
}

View File

@ -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 players chat, if
* the chat is activated.
* @param message the message to display.
*/
public abstract void sendMessage(BaseComponent message);
/**
* Display the provided message in the players 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 players 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 dont 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 players 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 dont 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 players 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 players 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 cant 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();
}
}

View File

@ -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);
}
}

View File

@ -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("Cant 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;
}
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.search; package fr.pandacube.lib.core.search;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; 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.Cache;
import com.google.common.cache.CacheBuilder; 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 * Utility class to manage searching among a set of

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.search; package fr.pandacube.lib.core.search;
import java.util.Set; import java.util.Set;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
@FunctionalInterface @FunctionalInterface
public interface Callback<T> { public interface Callback<T> {

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.measurement; package fr.pandacube.lib.core.util;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.Arrays; import java.util.Arrays;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
public class EnumUtil { public class EnumUtil {

View 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());
}
}
}

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
//****************************************************************************** //******************************************************************************
//*** //***

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.util.Objects; import java.util.Objects;
import java.util.function.ToIntBiFunction; import java.util.function.ToIntBiFunction;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.util.AbstractList; import java.util.AbstractList;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.measurement; package fr.pandacube.lib.core.util;
import java.text.DecimalFormat; import java.text.DecimalFormat;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
public class MinecraftWebUtil { public class MinecraftWebUtil {
@ -10,6 +10,8 @@ public class MinecraftWebUtil {
@param @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) public static String fromMinecraftColorCodeToHTML(char code_prefix, String ligne)
{ {
String color_char = "0123456789abcdefr"; String color_char = "0123456789abcdefr";

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.UUID; import java.util.UUID;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.measurement; package fr.pandacube.lib.core.util;
public class Tick { public class Tick {

View File

@ -1,4 +1,4 @@
package fr.pandacube.util.measurement; package fr.pandacube.lib.core.util;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -9,7 +9,9 @@ import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -17,22 +19,20 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import fr.pandacube.Pandacube; import fr.pandacube.lib.core.commands.SuggestionsSupplier;
import fr.pandacube.util.StringUtil;
import fr.pandacube.util.commands.SuggestionsSupplier;
public class TimeUtil { public class TimeUtil {
private static DateTimeFormatter cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Pandacube.LOCALE); private static DateTimeFormatter cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Locale.getDefault());
private static DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Pandacube.LOCALE); private static DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Locale.getDefault());
private static DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Pandacube.LOCALE); private static DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Locale.getDefault());
private static DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Pandacube.LOCALE); private static DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Locale.getDefault());
private static DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Pandacube.LOCALE); private static DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Locale.getDefault());
private static DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Pandacube.LOCALE); private static DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Locale.getDefault());
private static DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Pandacube.LOCALE); private static DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.getDefault());
private static DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Pandacube.LOCALE); private static DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Locale.getDefault());
@ -124,7 +124,7 @@ public class TimeUtil {
private static LocalDateTime toLocalDateTime(long msTime) { private static LocalDateTime toLocalDateTime(long msTime) {
return Instant.ofEpochMilli(msTime).atZone(Pandacube.TIMEZONE.toZoneId()).toLocalDateTime(); return Instant.ofEpochMilli(msTime).atZone(TimeZone.getDefault().toZoneId()).toLocalDateTime();
} }

View File

@ -1,4 +1,4 @@
package fr.pandacube.util; package fr.pandacube.lib.core.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -49,7 +49,7 @@ public class TypeConverter {
public static int toPrimInt(Object o) { public static int toPrimInt(Object o) {
Integer val = toInteger(o); Integer val = toInteger(o);
if (val == null) 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; return val;
} }

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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("%", "\\%");
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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>&lt;</code>" */
LT("<"),
/** Equivalent to SQL "<code>&lt;=</code>" */
LEQ("<="),
/** Equivalent to SQL "<code>!=</code>" */
NEQ("!=");
/* package */ final String sql;
private SQLComparator(String s) {
sql = s;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<>());
}
}

View File

@ -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
View 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
View File

@ -0,0 +1 @@
/target/

23
Paper/.project Normal file
View 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