Refactoring
This commit is contained in:
parent
87f2bf0b19
commit
5019788865
17
.project
Normal file
17
.project
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>pandalib-parent</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
2
.settings/org.eclipse.core.resources.prefs
Normal file
2
.settings/org.eclipse.core.resources.prefs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
encoding/<project>=UTF-8
|
4
.settings/org.eclipse.m2e.core.prefs
Normal file
4
.settings/org.eclipse.m2e.core.prefs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
activeProfiles=
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
resolveWorkspaceProjects=true
|
||||||
|
version=1
|
@ -1,6 +1,6 @@
|
|||||||
eclipse.preferences.version=1
|
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
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
package fr.pandacube;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
|
|
||||||
import fr.pandacube.util.text_display.Chat;
|
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
|
|
||||||
public class Pandacube {
|
|
||||||
|
|
||||||
public static final Locale LOCALE = Locale.FRANCE;
|
|
||||||
|
|
||||||
public static final TimeZone TIMEZONE = TimeZone.getTimeZone("Europe/Paris");
|
|
||||||
|
|
||||||
public static final Charset NETWORK_CHARSET = Charset.forName("UTF-8");
|
|
||||||
|
|
||||||
public static final int NETWORK_TCP_BUFFER_SIZE = 1024 * 1024;
|
|
||||||
|
|
||||||
public static final int NETWORK_TIMEOUT = 0; // no timeout (milli-seconds)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//public static final ChatColor CHAT_GREEN_1_NORMAL = ChatColor.of("#5f9765"); // h=126 s=23 l=48
|
|
||||||
|
|
||||||
public static final ChatColor CHAT_GREEN_1_NORMAL = ChatColor.of("#3db849"); // h=126 s=50 l=48
|
|
||||||
public static final ChatColor CHAT_GREEN_2 = ChatColor.of("#5ec969"); // h=126 s=50 l=58
|
|
||||||
public static final ChatColor CHAT_GREEN_3 = ChatColor.of("#85d68d"); // h=126 s=50 l=68
|
|
||||||
public static final ChatColor CHAT_GREEN_4 = ChatColor.of("#abe3b0"); // h=126 s=50 l=78
|
|
||||||
|
|
||||||
public static final ChatColor CHAT_GREEN_SATMAX = ChatColor.of("#00ff19"); // h=126 s=100 l=50
|
|
||||||
public static final ChatColor CHAT_GREEN_1_SAT = ChatColor.of("#20d532"); // h=126 s=50 l=48
|
|
||||||
public static final ChatColor CHAT_GREEN_2_SAT = ChatColor.of("#45e354"); // h=126 s=50 l=58
|
|
||||||
public static final ChatColor CHAT_GREEN_3_SAT = ChatColor.of("#71ea7d"); // h=126 s=50 l=68
|
|
||||||
public static final ChatColor CHAT_GREEN_4_SAT = ChatColor.of("#9df0a6"); // h=126 s=50 l=78
|
|
||||||
|
|
||||||
public static final ChatColor CHAT_BROWN_1 = ChatColor.of("#b26d3a"); // h=26 s=51 l=46
|
|
||||||
public static final ChatColor CHAT_BROWN_2 = ChatColor.of("#cd9265"); // h=26 s=51 l=60
|
|
||||||
public static final ChatColor CHAT_BROWN_3 = ChatColor.of("#e0bb9f"); // h=26 s=51 l=75
|
|
||||||
|
|
||||||
public static final ChatColor CHAT_BROWN_1_SAT = ChatColor.of("#b35c19"); // h=26 s=75 l=40
|
|
||||||
public static final ChatColor CHAT_BROWN_2_SAT = ChatColor.of("#e28136"); // h=26 s=51 l=55
|
|
||||||
public static final ChatColor CHAT_BROWN_3_SAT = ChatColor.of("#ecab79"); // h=26 s=51 l=70
|
|
||||||
|
|
||||||
public static final ChatColor CHAT_GRAY_MID = ChatColor.of("#888888");
|
|
||||||
|
|
||||||
public static final ChatColor CHAT_BROADCAST_COLOR = ChatColor.YELLOW;
|
|
||||||
|
|
||||||
|
|
||||||
public static final ChatColor CHAT_DECORATION_COLOR = CHAT_GREEN_1_NORMAL;
|
|
||||||
public static final char CHAT_DECORATION_CHAR = '-';
|
|
||||||
public static final ChatColor CHAT_URL_COLOR = CHAT_GREEN_1_NORMAL;
|
|
||||||
public static final ChatColor CHAT_COMMAND_COLOR = CHAT_GRAY_MID;
|
|
||||||
public static final ChatColor CHAT_COMMAND_HIGHLIGHTED_COLOR = ChatColor.WHITE;
|
|
||||||
public static final ChatColor CHAT_INFO_COLOR = CHAT_GREEN_4;
|
|
||||||
public static final ChatColor CHAT_SUCCESS_COLOR = CHAT_GREEN_SATMAX;
|
|
||||||
public static final ChatColor CHAT_FAILURE_COLOR = ChatColor.of("#ff3333");
|
|
||||||
public static final ChatColor CHAT_DATA_COLOR = CHAT_GRAY_MID;
|
|
||||||
|
|
||||||
|
|
||||||
public static final ChatColor CHAT_PM_PREFIX_DECORATION = Pandacube.CHAT_BROWN_2_SAT;
|
|
||||||
public static final ChatColor CHAT_PM_SELF_MESSAGE = Pandacube.CHAT_GREEN_2;
|
|
||||||
public static final ChatColor CHAT_PM_OTHER_MESSAGE = Pandacube.CHAT_GREEN_4;
|
|
||||||
|
|
||||||
|
|
||||||
public static final Chat CHAT_MESSAGE_PREFIX() {
|
|
||||||
return Chat.text("[")
|
|
||||||
.color(CHAT_BROADCAST_COLOR)
|
|
||||||
.thenDecoration("Pandacube")
|
|
||||||
.thenText("] ");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of decoration character to put between the text and the border of
|
|
||||||
* the line for left and right aligned text.
|
|
||||||
*/
|
|
||||||
public static final int CHAT_NB_CHAR_MARGIN = 1;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static {
|
|
||||||
// initialize class to avoid NCDFE when updating the plugin
|
|
||||||
@SuppressWarnings({ "deprecation", "unused" })
|
|
||||||
Class<?>
|
|
||||||
c1 = fr.pandacube.util.network_api.server.RequestAnalyser.class,
|
|
||||||
c2 = fr.pandacube.util.network_api.server.RequestAnalyser.BadRequestException.class,
|
|
||||||
c3 = fr.pandacube.util.network_api.server.Response.class,
|
|
||||||
c4 = fr.pandacube.util.text_display.ChatUtil.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
package fr.pandacube.util.text_display;
|
package fr.pandacube.lib.core.chat;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -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;
|
@ -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;
|
@ -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);
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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 {
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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> {
|
@ -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
|
@ -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;
|
@ -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("Can’t retrieve element count from database (The ResultSet may be empty)");
|
throw new DBException("Can’t retrieve element count from database (The ResultSet may be empty)");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static ResultSet customQueryStatement(String sql, List<Object> params) throws ORMException {
|
public static ResultSet customQueryStatement(String sql, List<Object> params) throws DBException {
|
||||||
try {
|
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() {}
|
||||||
|
|
||||||
}
|
}
|
@ -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
|
18
Core/src/main/java/fr/pandacube/lib/core/db/DBException.java
Normal file
18
Core/src/main/java/fr/pandacube/lib/core/db/DBException.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package fr.pandacube.lib.core.db;
|
||||||
|
|
||||||
|
public class DBException extends Exception {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public DBException(Throwable initCause) {
|
||||||
|
super(initCause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DBException(String message, Throwable initCause) {
|
||||||
|
super(message, initCause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DBException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package fr.pandacube.lib.core.db;
|
||||||
|
|
||||||
|
public class DBInitTableException extends DBException {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem) {
|
||||||
|
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem, Throwable t) {
|
||||||
|
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"), t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem, String message) {
|
||||||
|
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null") + ": " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.orm;
|
package fr.pandacube.lib.core.db;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.orm;
|
package fr.pandacube.lib.core.db;
|
||||||
|
|
||||||
import java.lang.reflect.Modifier;
|
import java.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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<>();
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -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> {
|
||||||
|
|
@ -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;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.orm;
|
package fr.pandacube.lib.core.db;
|
||||||
|
|
||||||
public class SQLType<T> {
|
public class SQLType<T> {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.orm;
|
package fr.pandacube.lib.core.db;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
306
Core/src/main/java/fr/pandacube/lib/core/db/SQLWhere.java
Normal file
306
Core/src/main/java/fr/pandacube/lib/core/db/SQLWhere.java
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
package fr.pandacube.lib.core.db;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import org.javatuples.Pair;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.util.Log;
|
||||||
|
|
||||||
|
public abstract class SQLWhere<E extends SQLElement<E>> {
|
||||||
|
|
||||||
|
public abstract Pair<String, List<Object>> toSQL() throws DBException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
try {
|
||||||
|
return toSQL().getValue0();
|
||||||
|
} catch (DBException e) {
|
||||||
|
Log.warning(e);
|
||||||
|
return "[SQLWhere.toString() error (see logs)]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLWhereAnd<E> and(SQLWhere<E> other) {
|
||||||
|
return new SQLWhereAnd<E>().and(this).and(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLWhereOr<E> or(SQLWhere<E> other) {
|
||||||
|
return new SQLWhereOr<E>().or(this).or(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E extends SQLElement<E>> SQLWhereAnd<E> and() {
|
||||||
|
return new SQLWhereAnd<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E extends SQLElement<E>> SQLWhereOr<E> or() {
|
||||||
|
return new SQLWhereOr<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String escapeLike(String str) {
|
||||||
|
return str.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static abstract class SQLWhereChain<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||||
|
|
||||||
|
private SQLBoolOp operator;
|
||||||
|
protected List<SQLWhere<E>> conditions = new ArrayList<>();
|
||||||
|
|
||||||
|
private SQLWhereChain(SQLBoolOp op) {
|
||||||
|
if (op == null) throw new IllegalArgumentException("op can't be null");
|
||||||
|
operator = op;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void add(SQLWhere<E> sqlWhere) {
|
||||||
|
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
|
||||||
|
conditions.add(sqlWhere);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<String, List<Object>> toSQL() throws DBException {
|
||||||
|
if (conditions.isEmpty()) {
|
||||||
|
throw new DBException("SQLWhereChain needs at least one element inside !");
|
||||||
|
}
|
||||||
|
|
||||||
|
String sql = "";
|
||||||
|
List<Object> params = new ArrayList<>();
|
||||||
|
boolean first = true;
|
||||||
|
|
||||||
|
for (SQLWhere<E> w : conditions) {
|
||||||
|
if (!first) sql += " " + operator.sql + " ";
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
Pair<String, List<Object>> ret = w.toSQL();
|
||||||
|
sql += "(" + ret.getValue0() + ")";
|
||||||
|
params.addAll(ret.getValue1());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Pair<>(sql, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum SQLBoolOp {
|
||||||
|
/** Equivalent to SQL "<code>AND</code>" */
|
||||||
|
AND("AND"),
|
||||||
|
/** Equivalent to SQL "<code>OR</code>" */
|
||||||
|
OR("OR");
|
||||||
|
/* package */ final String sql;
|
||||||
|
|
||||||
|
private SQLBoolOp(String s) {
|
||||||
|
sql = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static class SQLWhereAnd<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
||||||
|
|
||||||
|
private SQLWhereAnd() {
|
||||||
|
super(SQLBoolOp.AND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLWhereAnd<E> and(SQLWhere<E> other) {
|
||||||
|
add(other);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static class SQLWhereOr<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
||||||
|
|
||||||
|
private SQLWhereOr() {
|
||||||
|
super(SQLBoolOp.OR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLWhereOr<E> or(SQLWhere<E> other) {
|
||||||
|
add(other);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ static class SQLWhereComp<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||||
|
|
||||||
|
private SQLField<E, ?> left;
|
||||||
|
private SQLComparator comp;
|
||||||
|
private Object right;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare a field with a value
|
||||||
|
*
|
||||||
|
* @param l the field at left of the comparison operator. Can't be null
|
||||||
|
* @param c the comparison operator, can't be null
|
||||||
|
* @param r the value at right of the comparison operator. Can't be null
|
||||||
|
*/
|
||||||
|
/* package */ <T> SQLWhereComp(SQLField<E, T> l, SQLComparator c, T r) {
|
||||||
|
if (l == null || r == null || c == null)
|
||||||
|
throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null");
|
||||||
|
left = l;
|
||||||
|
comp = c;
|
||||||
|
right = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<String, List<Object>> toSQL() throws DBException {
|
||||||
|
List<Object> params = new ArrayList<>();
|
||||||
|
SQLElement.addValueToSQLObjectList(params, left, right);
|
||||||
|
return new Pair<>("`" + left.getName() + "` " + comp.sql + " ? ", params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ enum SQLComparator {
|
||||||
|
/** Equivalent to SQL "<code>=</code>" */
|
||||||
|
EQ("="),
|
||||||
|
/** Equivalent to SQL "<code>></code>" */
|
||||||
|
GT(">"),
|
||||||
|
/** Equivalent to SQL "<code>>=</code>" */
|
||||||
|
GEQ(">="),
|
||||||
|
/** Equivalent to SQL "<code><</code>" */
|
||||||
|
LT("<"),
|
||||||
|
/** Equivalent to SQL "<code><=</code>" */
|
||||||
|
LEQ("<="),
|
||||||
|
/** Equivalent to SQL "<code>!=</code>" */
|
||||||
|
NEQ("!=");
|
||||||
|
|
||||||
|
/* package */ final String sql;
|
||||||
|
|
||||||
|
private SQLComparator(String s) {
|
||||||
|
sql = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ static class SQLWhereIn<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||||
|
|
||||||
|
private SQLField<E, ?> field;
|
||||||
|
private Collection<?> values;
|
||||||
|
|
||||||
|
/* package */ <T> SQLWhereIn(SQLField<E, T> f, Collection<T> v) {
|
||||||
|
if (f == null || v == null)
|
||||||
|
throw new IllegalArgumentException("All arguments for SQLWhereIn constructor can't be null");
|
||||||
|
field = f;
|
||||||
|
values = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<String, List<Object>> toSQL() throws DBException {
|
||||||
|
List<Object> params = new ArrayList<>();
|
||||||
|
|
||||||
|
if (values.isEmpty())
|
||||||
|
return new Pair<>(" 1=0 ", params);
|
||||||
|
|
||||||
|
for (Object v : values)
|
||||||
|
SQLElement.addValueToSQLObjectList(params, field, v);
|
||||||
|
|
||||||
|
char[] questions = new char[values.size() == 0 ? 0 : (values.size() * 2 - 1)];
|
||||||
|
for (int i = 0; i < questions.length; i++)
|
||||||
|
questions[i] = i % 2 == 0 ? '?' : ',';
|
||||||
|
|
||||||
|
return new Pair<>("`" + field.getName() + "` IN (" + new String(questions) + ") ", params);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ static class SQLWhereLike<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||||
|
|
||||||
|
private SQLField<E, ?> field;
|
||||||
|
private String likeExpr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare a field with a value
|
||||||
|
*
|
||||||
|
* @param f the field at left of the LIKE keyword. Can't be null
|
||||||
|
* @param like the like expression.
|
||||||
|
*/
|
||||||
|
/* package */ SQLWhereLike(SQLField<E, ?> f, String like) {
|
||||||
|
if (f == null || like == null)
|
||||||
|
throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null");
|
||||||
|
field = f;
|
||||||
|
likeExpr = like;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<String, List<Object>> toSQL() {
|
||||||
|
ArrayList<Object> params = new ArrayList<>();
|
||||||
|
params.add(likeExpr);
|
||||||
|
return new Pair<>("`" + field.getName() + "` LIKE ? ", params);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ static class SQLWhereNull<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||||
|
|
||||||
|
private SQLField<E, ?> fild;
|
||||||
|
private boolean nulll;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition.
|
||||||
|
*
|
||||||
|
* @param field the field to check null / not null state
|
||||||
|
* @param isNull true if we want to ckeck if "IS NULL", or false to check if
|
||||||
|
* "IS NOT NULL"
|
||||||
|
*/
|
||||||
|
/* package */ SQLWhereNull(SQLField<E, ?> field, boolean isNull) {
|
||||||
|
if (field == null) throw new IllegalArgumentException("field can't be null");
|
||||||
|
if (!field.canBeNull) Log.getLogger().log(Level.WARNING,
|
||||||
|
"Useless : Trying to check IS [NOT] NULL on the field " + field.getSQLElementType().getName() + "#"
|
||||||
|
+ field.getName() + " which is declared in the ORM as 'can't be null'");
|
||||||
|
fild = field;
|
||||||
|
nulll = isNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<String, List<Object>> toSQL() {
|
||||||
|
return new Pair<>("`" + fild.getName() + "` IS " + ((nulll) ? "NULL" : "NOT NULL"), new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.net;
|
package fr.pandacube.lib.core.net;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -1,13 +1,14 @@
|
|||||||
package fr.pandacube.util.net;
|
package fr.pandacube.lib.core.net;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.net;
|
package fr.pandacube.lib.core.net;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.net;
|
package fr.pandacube.lib.core.net;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -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> {
|
@ -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")
|
@ -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;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.net;
|
package fr.pandacube.lib.core.net;
|
||||||
|
|
||||||
public interface PSocketConnectionListener {
|
public interface PSocketConnectionListener {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.network_api.client;
|
package fr.pandacube.lib.core.network_api.client;
|
||||||
|
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.network_api.client;
|
package fr.pandacube.lib.core.network_api.client;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.network_api.client;
|
package fr.pandacube.lib.core.network_api.client;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
@ -1,11 +1,11 @@
|
|||||||
package fr.pandacube.util.network_api.server;
|
package fr.pandacube.lib.core.network_api.server;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.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 {
|
@ -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;
|
@ -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
|
@ -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;
|
@ -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;
|
||||||
|
|
@ -0,0 +1,167 @@
|
|||||||
|
package fr.pandacube.lib.core.permissions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.chat.ChatUtil.DisplayTreeNode;
|
||||||
|
import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedEntity;
|
||||||
|
import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType;
|
||||||
|
|
||||||
|
public abstract class PermEntity {
|
||||||
|
protected final String name;
|
||||||
|
protected final EntityType type;
|
||||||
|
protected PermEntity(String n, EntityType t) {
|
||||||
|
name = n; type = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract CachedEntity getBackendEntity();
|
||||||
|
public abstract List<PermGroup> getInheritances();
|
||||||
|
public abstract List<String> getInheritancesString();
|
||||||
|
public abstract String getName();
|
||||||
|
|
||||||
|
public String getInternalName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the current entity inherits directly or indirectly from the specified group
|
||||||
|
* @param group the group to search for
|
||||||
|
* @param recursive true to search in the inheritance tree, or false to search only in the inheritance list of the current entity.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean inheritsFromGroup(String group, boolean recursive) {
|
||||||
|
if (group == null)
|
||||||
|
return false;
|
||||||
|
return getInheritances().stream().anyMatch(g -> g.name.equals(group) || (recursive && g.inheritsFromGroup(group, recursive)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrefix() {
|
||||||
|
return Permissions.resolver.getEffectivePrefix(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getSelfPrefix() {
|
||||||
|
return getBackendEntity().getSelfPrefix();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public DisplayTreeNode debugPrefix() {
|
||||||
|
return Permissions.resolver.debugPrefix(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setSelfPrefix(String prefix) {
|
||||||
|
Permissions.backendWriter.setSelfPrefix(name, type, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getSuffix() {
|
||||||
|
return Permissions.resolver.getEffectiveSuffix(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelfSuffix() {
|
||||||
|
return getBackendEntity().getSelfSuffix();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public DisplayTreeNode debugSuffix() {
|
||||||
|
return Permissions.resolver.debugSuffix(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setSelfSuffix(String suffix) {
|
||||||
|
Permissions.backendWriter.setSelfSuffix(name, type, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Boolean hasPermission(String permission) {
|
||||||
|
return Permissions.resolver.getEffectivePermission(name, type, permission, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean hasPermission(String permission, String server) {
|
||||||
|
return Permissions.resolver.getEffectivePermission(name, type, permission, server, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean hasPermission(String permission, String server, String world) {
|
||||||
|
return Permissions.resolver.getEffectivePermission(name, type, permission, server, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public DisplayTreeNode debugPermission(String permission) {
|
||||||
|
return Permissions.resolver.debugPermission(name, type, permission, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DisplayTreeNode debugPermission(String permission, String server) {
|
||||||
|
return Permissions.resolver.debugPermission(name, type, permission, server, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DisplayTreeNode debugPermission(String permission, String server, String world) {
|
||||||
|
return Permissions.resolver.debugPermission(name, type, permission, server, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addSelfPermission(String permission) {
|
||||||
|
Permissions.backendWriter.addSelfPermission(name, type, permission, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSelfPermission(String permission, String server) {
|
||||||
|
Permissions.backendWriter.addSelfPermission(name, type, permission, server, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSelfPermission(String permission, String server, String world) {
|
||||||
|
Permissions.backendWriter.addSelfPermission(name, type, permission, server, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void removeSelfPermission(String permission) {
|
||||||
|
Permissions.backendWriter.removeSelfPermission(name, type, permission, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeSelfPermission(String permission, String server) {
|
||||||
|
Permissions.backendWriter.removeSelfPermission(name, type, permission, server, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeSelfPermission(String permission, String server, String world) {
|
||||||
|
Permissions.backendWriter.removeSelfPermission(name, type, permission, server, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSelfPermissionsCount() {
|
||||||
|
return getSelfPermissionsServerWorldKeys().stream()
|
||||||
|
.mapToInt(key -> getSelfPermissions(key.server, key.world).size())
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<ServerWorldKey> getSelfPermissionsServerWorldKeys() {
|
||||||
|
return getBackendEntity().getSelfPermissionsServerWorldKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSelfPermissions() {
|
||||||
|
return getBackendEntity().getSelfPermissions(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSelfPermissions(String server) {
|
||||||
|
return getBackendEntity().getSelfPermissions(server, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSelfPermissions(String server, String world) {
|
||||||
|
return getBackendEntity().getSelfPermissions(server, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null || !(obj instanceof PermEntity))
|
||||||
|
return false;
|
||||||
|
PermEntity o = (PermEntity) obj;
|
||||||
|
return Objects.equals(name, o.name) && type == o.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package fr.pandacube.lib.core.permissions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedGroup;
|
||||||
|
import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType;
|
||||||
|
|
||||||
|
public class PermGroup extends PermEntity {
|
||||||
|
/* package */ PermGroup(String name) {
|
||||||
|
super(name, EntityType.Group);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected CachedGroup getBackendEntity() {
|
||||||
|
return Permissions.backendReader.getCachedGroup(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return getInternalName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PermGroup> getInheritances() {
|
||||||
|
return fromCachedGroups(getBackendEntity().inheritances);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getInheritancesString() {
|
||||||
|
return getBackendEntity().inheritances.stream()
|
||||||
|
.map(cg -> cg.name)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDefault() {
|
||||||
|
return getBackendEntity().deflt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefault(boolean deflt) {
|
||||||
|
Permissions.backendWriter.setGroupDefault(name, deflt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addInheritance(String group) {
|
||||||
|
Permissions.backendWriter.addInheritance(name, type, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addInheritance(PermGroup group) {
|
||||||
|
addInheritance(group.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeInheritance(String group) {
|
||||||
|
Permissions.backendWriter.removeInheritance(name, type, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeInheritance(PermGroup group) {
|
||||||
|
removeInheritance(group.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ static List<PermGroup> fromCachedGroups(List<CachedGroup> in) {
|
||||||
|
return in.stream()
|
||||||
|
.map(cg -> Permissions.getGroup(cg.name))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
package fr.pandacube.lib.core.permissions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedPlayer;
|
||||||
|
import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType;
|
||||||
|
import fr.pandacube.lib.core.players.PlayerFinder;
|
||||||
|
|
||||||
|
public class PermPlayer extends PermEntity {
|
||||||
|
private final UUID playerId;
|
||||||
|
/* package */ PermPlayer(UUID id) {
|
||||||
|
super(id.toString(), EntityType.User);
|
||||||
|
playerId = id;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected CachedPlayer getBackendEntity() {
|
||||||
|
return Permissions.backendReader.getCachedPlayer(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PermGroup> getInheritances() {
|
||||||
|
return PermGroup.fromCachedGroups(getBackendEntity().groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getInheritancesString() {
|
||||||
|
return getBackendEntity().groups.stream()
|
||||||
|
.map(cg -> cg.name)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getPlayerId() {
|
||||||
|
return playerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cachedPlayerName;
|
||||||
|
@Override
|
||||||
|
public synchronized String getName() {
|
||||||
|
if (cachedPlayerName == null)
|
||||||
|
cachePlayerName();
|
||||||
|
return cachedPlayerName;
|
||||||
|
}
|
||||||
|
private void cachePlayerName() {
|
||||||
|
cachedPlayerName = PlayerFinder.getLastKnownName(playerId);
|
||||||
|
if (cachedPlayerName == null)
|
||||||
|
cachedPlayerName = playerId.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link #getInheritances()}.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<PermGroup> getGroups() {
|
||||||
|
return getInheritances();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link #getInheritances()}.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> getGroupsString() {
|
||||||
|
return getInheritancesString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the player is directly part of a group.
|
||||||
|
* This is equivalent to {@link #inheritsFromGroup(String, boolean) inheritsFromGroup(group, false)}
|
||||||
|
* @param group the group to search for
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isInGroup(String group) {
|
||||||
|
return inheritsFromGroup(group, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUsingDefaultGroups() {
|
||||||
|
return getBackendEntity().usingDefaultGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroup(String group) {
|
||||||
|
Permissions.backendWriter.setInheritance(name, type, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroup(PermGroup group) {
|
||||||
|
setGroup(group.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addGroup(String group) {
|
||||||
|
Permissions.backendWriter.addInheritance(name, type, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addGroup(PermGroup group) {
|
||||||
|
addGroup(group.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeGroup(String group) {
|
||||||
|
Permissions.backendWriter.removeInheritance(name, type, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeGroup(PermGroup group) {
|
||||||
|
removeGroup(group.name);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package fr.pandacube.lib.core.permissions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.db.DB;
|
||||||
|
import fr.pandacube.lib.core.db.DBException;
|
||||||
|
import fr.pandacube.lib.core.util.Log;
|
||||||
|
|
||||||
|
public class Permissions {
|
||||||
|
|
||||||
|
/* package */ static PermissionsCachedBackendReader backendReader;
|
||||||
|
/* package */ static PermissionsResolver resolver;
|
||||||
|
/* package */ static PermissionsBackendWriter backendWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the permission system.
|
||||||
|
* The connection to the database needs to be initialized first, using {@link DB#init(DBConnection, String)}.
|
||||||
|
* @throws DBException
|
||||||
|
*/
|
||||||
|
public static void init() throws DBException {
|
||||||
|
if (backendReader != null)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
DB.initTable(SQLPermissions.class);
|
||||||
|
backendReader = new PermissionsCachedBackendReader();
|
||||||
|
resolver = new PermissionsResolver(backendReader);
|
||||||
|
backendWriter = new PermissionsBackendWriter();
|
||||||
|
} catch (Exception e) {
|
||||||
|
backendReader = null;
|
||||||
|
resolver = null;
|
||||||
|
backendWriter = null;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkInitialized() {
|
||||||
|
if (backendReader == null) {
|
||||||
|
throw new IllegalStateException("Permissions system not initialized. Check the server logs to check if there is an error during the startup, and check if the init() method is called properly.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearPlayerCache(UUID playerId) {
|
||||||
|
checkInitialized();
|
||||||
|
backendReader.clearPlayerCache(playerId);
|
||||||
|
resolver.clearPlayerFromCache(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearCache(Runnable then) {
|
||||||
|
checkInitialized();
|
||||||
|
backendReader.clearAndResetCacheAsync(() -> {
|
||||||
|
resolver.clearCache();
|
||||||
|
if (then != null)
|
||||||
|
then.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static PermPlayer getPlayer(UUID playerId) {
|
||||||
|
checkInitialized();
|
||||||
|
return new PermPlayer(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void precachePlayerAsync(UUID playerId) {
|
||||||
|
checkInitialized();
|
||||||
|
Thread t = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
backendReader.getCachedPlayer(playerId);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
Log.warning("Can’t init player cache asynchronously: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}, "Async permissions player cache loader");
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PermGroup getGroup(String name) {
|
||||||
|
checkInitialized();
|
||||||
|
return new PermGroup(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<PermGroup> getGroups() {
|
||||||
|
checkInitialized();
|
||||||
|
return PermGroup.fromCachedGroups(backendReader.getGroups());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<PermGroup> getDefaultGroups() {
|
||||||
|
checkInitialized();
|
||||||
|
return PermGroup.fromCachedGroups(backendReader.getDefaultGroups());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getFullPermissionsList() {
|
||||||
|
return backendReader.getFullPermissionsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,293 @@
|
|||||||
|
package fr.pandacube.lib.core.permissions;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.db.DB;
|
||||||
|
import fr.pandacube.lib.core.db.DBException;
|
||||||
|
import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType;
|
||||||
|
|
||||||
|
/* package */ class PermissionsBackendWriter {
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ void addSelfPermission(String name, EntityType type, String permission, String server, String world) {
|
||||||
|
Preconditions.checkNotNull(name, "name cannot be null");
|
||||||
|
Preconditions.checkNotNull(type, "type cannot be null");
|
||||||
|
Preconditions.checkNotNull(permission, "permission cannot be null");
|
||||||
|
Preconditions.checkArgument(world == null || server != null, "world not null but server is null");
|
||||||
|
name = name.toLowerCase();
|
||||||
|
permission = permission.toLowerCase();
|
||||||
|
if (server != null) server = server.toLowerCase();
|
||||||
|
if (world != null) world = world.toLowerCase();
|
||||||
|
|
||||||
|
if (hasEntry(name, type, "permissions", permission, server, world))
|
||||||
|
throw new IllegalStateException("Permission already set");
|
||||||
|
addEntry(name, type, "permissions", permission, server, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void removeSelfPermission(String name, EntityType type, String permission, String server, String world) {
|
||||||
|
Preconditions.checkNotNull(name, "name cannot be null");
|
||||||
|
Preconditions.checkNotNull(type, "type cannot be null");
|
||||||
|
Preconditions.checkNotNull(permission, "permission cannot be null");
|
||||||
|
Preconditions.checkArgument(world == null || server != null, "world not null but server is null");
|
||||||
|
name = name.toLowerCase();
|
||||||
|
permission = permission.toLowerCase();
|
||||||
|
if (server != null) server = server.toLowerCase();
|
||||||
|
if (world != null) world = world.toLowerCase();
|
||||||
|
|
||||||
|
if (!deleteEntry(name, type, "permissions", permission, server, world))
|
||||||
|
throw new IllegalStateException("Permission was not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ void setGroupDefault(String name, boolean deflt) {
|
||||||
|
Preconditions.checkNotNull(name, "name cannot be null");
|
||||||
|
name = name.toLowerCase();
|
||||||
|
try {
|
||||||
|
SQLPermissions entry = DB.getFirst(SQLPermissions.class,
|
||||||
|
SQLPermissions.name.like(name)
|
||||||
|
.and(SQLPermissions.type.eq(EntityType.Group.getCode()))
|
||||||
|
.and(SQLPermissions.key.like("default"))
|
||||||
|
);
|
||||||
|
if (entry == null && !deflt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (entry != null && deflt) {
|
||||||
|
// update
|
||||||
|
if ("true".equals(entry.get(SQLPermissions.value)))
|
||||||
|
return;
|
||||||
|
entry.set(SQLPermissions.value, "true");
|
||||||
|
entry.save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (entry != null && !deflt) {
|
||||||
|
// delete
|
||||||
|
entry.delete();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// insert
|
||||||
|
addEntry(name, EntityType.Group, "default", "true", null, null);
|
||||||
|
}
|
||||||
|
} catch (DBException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ void setSelfPrefix(String name, EntityType type, String prefix) {
|
||||||
|
Preconditions.checkNotNull(name, "name cannot be null");
|
||||||
|
Preconditions.checkNotNull(type, "type cannot be null");
|
||||||
|
name = name.toLowerCase();
|
||||||
|
|
||||||
|
try {
|
||||||
|
SQLPermissions entry = DB.getFirst(SQLPermissions.class,
|
||||||
|
SQLPermissions.name.like(name)
|
||||||
|
.and(SQLPermissions.type.eq(type.getCode()))
|
||||||
|
.and(SQLPermissions.key.like("prefix"))
|
||||||
|
);
|
||||||
|
if (entry == null && prefix == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (entry != null && prefix != null) {
|
||||||
|
// update
|
||||||
|
entry.set(SQLPermissions.value, prefix);
|
||||||
|
entry.save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (entry != null && prefix == null) {
|
||||||
|
// delete
|
||||||
|
entry.delete();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// insert
|
||||||
|
addEntry(name, type, "prefix", prefix, null, null);
|
||||||
|
}
|
||||||
|
} catch (DBException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void setSelfSuffix(String name, EntityType type, String suffix) {
|
||||||
|
Preconditions.checkNotNull(name, "name cannot be null");
|
||||||
|
Preconditions.checkNotNull(type, "type cannot be null");
|
||||||
|
name = name.toLowerCase();
|
||||||
|
|
||||||
|
try {
|
||||||
|
SQLPermissions entry = DB.getFirst(SQLPermissions.class,
|
||||||
|
SQLPermissions.name.like(name)
|
||||||
|
.and(SQLPermissions.type.eq(type.getCode()))
|
||||||
|
.and(SQLPermissions.key.like("suffix"))
|
||||||
|
);
|
||||||
|
if (entry == null && suffix == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (entry != null && suffix != null) {
|
||||||
|
// update
|
||||||
|
entry.set(SQLPermissions.value, suffix);
|
||||||
|
entry.save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (entry != null && suffix == null) {
|
||||||
|
// delete
|
||||||
|
entry.delete();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// insert
|
||||||
|
addEntry(name, type, "suffix", suffix, null, null);
|
||||||
|
}
|
||||||
|
} catch (DBException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ void addInheritance(String name, EntityType type, String inheritance) {
|
||||||
|
Preconditions.checkNotNull(name, "name cannot be null");
|
||||||
|
Preconditions.checkNotNull(type, "type cannot be null");
|
||||||
|
Preconditions.checkNotNull(inheritance, "inheritance cannot be null");
|
||||||
|
name = name.toLowerCase();
|
||||||
|
inheritance = inheritance.toLowerCase();
|
||||||
|
String key = type == EntityType.Group ? "inheritances" : "groups";
|
||||||
|
|
||||||
|
try {
|
||||||
|
SQLPermissions entry = DB.getFirst(SQLPermissions.class,
|
||||||
|
SQLPermissions.name.like(name)
|
||||||
|
.and(SQLPermissions.type.eq(type.getCode()))
|
||||||
|
.and(SQLPermissions.key.like(key))
|
||||||
|
.and(SQLPermissions.value.like(inheritance))
|
||||||
|
);
|
||||||
|
if (entry != null)
|
||||||
|
throw new IllegalStateException("Inheritance already set");
|
||||||
|
addEntry(name, type, key, inheritance, null, null);
|
||||||
|
} catch (DBException e) {
|
||||||
|
new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void removeInheritance(String name, EntityType type, String inheritance) {
|
||||||
|
Preconditions.checkNotNull(name, "name cannot be null");
|
||||||
|
Preconditions.checkNotNull(type, "type cannot be null");
|
||||||
|
Preconditions.checkNotNull(inheritance, "inheritance cannot be null");
|
||||||
|
name = name.toLowerCase();
|
||||||
|
inheritance = inheritance.toLowerCase();
|
||||||
|
String key = type == EntityType.Group ? "inheritances" : "groups";
|
||||||
|
|
||||||
|
try {
|
||||||
|
int deleted = DB.delete(SQLPermissions.class,
|
||||||
|
SQLPermissions.name.like(name)
|
||||||
|
.and(SQLPermissions.type.eq(type.getCode()))
|
||||||
|
.and(SQLPermissions.key.like(key))
|
||||||
|
.and(SQLPermissions.value.like(inheritance))
|
||||||
|
);
|
||||||
|
if (deleted == 0)
|
||||||
|
throw new IllegalStateException("Inheritance was not set");
|
||||||
|
} catch (DBException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void setInheritance(String name, EntityType type, String inheritance) {
|
||||||
|
Preconditions.checkNotNull(name, "name cannot be null");
|
||||||
|
Preconditions.checkNotNull(type, "type cannot be null");
|
||||||
|
Preconditions.checkNotNull(inheritance, "inheritance cannot be null");
|
||||||
|
name = name.toLowerCase();
|
||||||
|
inheritance = inheritance.toLowerCase();
|
||||||
|
String key = type == EntityType.Group ? "inheritances" : "groups";
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB.delete(SQLPermissions.class,
|
||||||
|
SQLPermissions.name.like(name)
|
||||||
|
.and(SQLPermissions.type.eq(type.getCode()))
|
||||||
|
.and(SQLPermissions.key.like(key))
|
||||||
|
);
|
||||||
|
addEntry(name, type, key, inheritance, null, null);
|
||||||
|
} catch (DBException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private boolean deleteEntry(String name, EntityType type,
|
||||||
|
String key, String value,
|
||||||
|
String server, String world) {
|
||||||
|
try {
|
||||||
|
return DB.delete(SQLPermissions.class,
|
||||||
|
SQLPermissions.name.like(name)
|
||||||
|
.and(SQLPermissions.type.eq(type.getCode()))
|
||||||
|
.and(SQLPermissions.key.like(key))
|
||||||
|
.and(SQLPermissions.value.like(value))
|
||||||
|
.and(server == null ? SQLPermissions.server.isNull() : SQLPermissions.server.like(server))
|
||||||
|
.and(world == null ? SQLPermissions.world.isNull() : SQLPermissions.world.like(world))
|
||||||
|
) >= 1;
|
||||||
|
} catch (DBException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasEntry(String name, EntityType type,
|
||||||
|
String key, String value,
|
||||||
|
String server, String world) {
|
||||||
|
return getEntry(name, type, key, value, server, world) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SQLPermissions getEntry(String name, EntityType type,
|
||||||
|
String key, String value,
|
||||||
|
String server, String world) {
|
||||||
|
try {
|
||||||
|
return DB.getFirst(SQLPermissions.class,
|
||||||
|
SQLPermissions.name.like(name)
|
||||||
|
.and(SQLPermissions.type.eq(type.getCode()))
|
||||||
|
.and(SQLPermissions.key.like(key))
|
||||||
|
.and(SQLPermissions.value.like(value))
|
||||||
|
.and(server == null ? SQLPermissions.server.isNull() : SQLPermissions.server.like(server))
|
||||||
|
.and(world == null ? SQLPermissions.world.isNull() : SQLPermissions.world.like(world))
|
||||||
|
);
|
||||||
|
} catch (DBException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addEntry(String name, EntityType type,
|
||||||
|
String key, String value,
|
||||||
|
String server, String world) {
|
||||||
|
SQLPermissions entry = new SQLPermissions()
|
||||||
|
.set(SQLPermissions.name, name)
|
||||||
|
.set(SQLPermissions.type, type.getCode())
|
||||||
|
.set(SQLPermissions.key, key)
|
||||||
|
.set(SQLPermissions.value, value)
|
||||||
|
.set(SQLPermissions.server, server)
|
||||||
|
.set(SQLPermissions.world, world);
|
||||||
|
try {
|
||||||
|
entry.save();
|
||||||
|
} catch (DBException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,307 @@
|
|||||||
|
package fr.pandacube.lib.core.permissions;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.google.common.cache.Cache;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.db.DB;
|
||||||
|
import fr.pandacube.lib.core.db.DBException;
|
||||||
|
import fr.pandacube.lib.core.db.SQLElementList;
|
||||||
|
import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType;
|
||||||
|
import fr.pandacube.lib.core.util.Log;
|
||||||
|
|
||||||
|
/* package */ class PermissionsCachedBackendReader
|
||||||
|
{
|
||||||
|
/* package */ PermissionsCachedBackendReader() throws DBException {
|
||||||
|
clearAndResetCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private Cache<UUID, CachedPlayer> usersCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
private Set<String> fullPermissionsList = new TreeSet<String>();
|
||||||
|
|
||||||
|
/* package */ synchronized List<String> getFullPermissionsList() {
|
||||||
|
return new ArrayList<>(fullPermissionsList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ synchronized void clearPlayerCache(UUID playerId) {
|
||||||
|
usersCache.invalidate(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ synchronized CachedPlayer getCachedPlayer(UUID playerId) {
|
||||||
|
try {
|
||||||
|
return usersCache.get(playerId, () -> {
|
||||||
|
try {
|
||||||
|
return initPlayer(playerId);
|
||||||
|
} catch (DBException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CachedPlayer initPlayer(UUID playerId) throws DBException {
|
||||||
|
|
||||||
|
SQLElementList<SQLPermissions> playerData = DB.getAll(SQLPermissions.class,
|
||||||
|
SQLPermissions.type.eq(EntityType.User.getCode())
|
||||||
|
.and(SQLPermissions.name.like(playerId.toString()))
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, List<SQLPermissions>> playerRawData = playerData.stream()
|
||||||
|
.collect(
|
||||||
|
Collectors.groupingBy(e -> e.get(SQLPermissions.key),
|
||||||
|
LinkedHashMap::new,
|
||||||
|
Collectors.toList())
|
||||||
|
);
|
||||||
|
|
||||||
|
String playerSelfPrefix = null;
|
||||||
|
if (playerRawData.containsKey("prefix")) {
|
||||||
|
playerSelfPrefix = playerRawData.get("prefix").stream()
|
||||||
|
.map(e -> e.get(SQLPermissions.value))
|
||||||
|
.collect(Collectors.joining());
|
||||||
|
}
|
||||||
|
|
||||||
|
String playerSelfSuffix = null;
|
||||||
|
if (playerRawData.containsKey("suffix")) {
|
||||||
|
playerSelfSuffix = playerRawData.get("suffix").stream()
|
||||||
|
.map(e -> e.get(SQLPermissions.value))
|
||||||
|
.collect(Collectors.joining());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<ServerWorldKey, List<String>> playerSelfPerms = new LinkedHashMap<>();
|
||||||
|
if (playerRawData.containsKey("permissions")) {
|
||||||
|
playerSelfPerms = playerRawData.get("permissions").stream()
|
||||||
|
.peek(e -> {
|
||||||
|
String value = e.get(SQLPermissions.value);
|
||||||
|
fullPermissionsList.add(value.substring(value.startsWith("-") ? 1 : 0));
|
||||||
|
})
|
||||||
|
.collect(Collectors.groupingBy(e -> new ServerWorldKey(e.get(SQLPermissions.server), e.get(SQLPermissions.world)),
|
||||||
|
LinkedHashMap::new,
|
||||||
|
Collectors.mapping(e -> e.get(SQLPermissions.value),
|
||||||
|
Collectors.toList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CachedPlayer player = new CachedPlayer(playerId, playerSelfPrefix, playerSelfSuffix, playerSelfPerms);
|
||||||
|
|
||||||
|
if (playerRawData.containsKey("groups")) {
|
||||||
|
playerRawData.get("groups").stream()
|
||||||
|
.map(e -> e.get(SQLPermissions.value))
|
||||||
|
.forEach(g -> {
|
||||||
|
player.groups.add(getCachedGroup(g));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.groups.isEmpty()) {
|
||||||
|
player.usingDefaultGroups = true;
|
||||||
|
getDefaultGroups().forEach(player.groups::add);
|
||||||
|
}
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private Map<String, CachedGroup> groupsCache = new LinkedHashMap<>();
|
||||||
|
private boolean cacheIsUpdating = false;
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ synchronized CachedGroup getCachedGroup(String group) {
|
||||||
|
return groupsCache.getOrDefault(group, new CachedGroup(group, null, null, false, new LinkedHashMap<>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ synchronized List<CachedGroup> getDefaultGroups() {
|
||||||
|
return groupsCache.values().stream()
|
||||||
|
.filter(g -> g.deflt)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CachedGroup> getGroups() {
|
||||||
|
return new ArrayList<>(groupsCache.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void clearAndResetCacheAsync(Runnable then) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (cacheIsUpdating)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Thread t = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
clearAndResetCache();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Log.severe(e);
|
||||||
|
}
|
||||||
|
if (then != null)
|
||||||
|
then.run();
|
||||||
|
}, "Permissions Backend Group Cache Updater");
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearAndResetCache() throws DBException {
|
||||||
|
synchronized (this) {
|
||||||
|
if (cacheIsUpdating)
|
||||||
|
return;
|
||||||
|
cacheIsUpdating = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, CachedGroup> newData = new LinkedHashMap<>();
|
||||||
|
Set<String> newFullPermissionsList = new TreeSet<>();
|
||||||
|
|
||||||
|
SQLElementList<SQLPermissions> groupData = DB.getAll(SQLPermissions.class, SQLPermissions.type.eq(EntityType.Group.getCode()));
|
||||||
|
|
||||||
|
Map<String, Map<String, List<SQLPermissions>>> groupsRawData = groupData.stream()
|
||||||
|
.collect(
|
||||||
|
Collectors.groupingBy(e -> e.get(SQLPermissions.name),
|
||||||
|
LinkedHashMap::new,
|
||||||
|
Collectors.groupingBy(e -> e.get(SQLPermissions.key),
|
||||||
|
LinkedHashMap::new,
|
||||||
|
Collectors.toList())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (String groupName : groupsRawData.keySet()) {
|
||||||
|
initGroup(groupName, groupsRawData, newData, newFullPermissionsList);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
groupsCache.clear();
|
||||||
|
groupsCache.putAll(newData);
|
||||||
|
cacheIsUpdating = false;
|
||||||
|
usersCache.invalidateAll();
|
||||||
|
fullPermissionsList = newFullPermissionsList;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
synchronized (this) {
|
||||||
|
cacheIsUpdating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initGroup(String groupName, Map<String, Map<String, List<SQLPermissions>>> groupsRawData, Map<String, CachedGroup> newData, Set<String> newFullPermissionsList) {
|
||||||
|
if (newData.containsKey(groupName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Map<String, List<SQLPermissions>> groupRawData = groupsRawData.getOrDefault(groupName, new LinkedHashMap<>());
|
||||||
|
|
||||||
|
boolean groupDefault = groupRawData.containsKey("default")
|
||||||
|
? "true".equals(groupRawData.get("default").get(0).get(SQLPermissions.value))
|
||||||
|
: false;
|
||||||
|
|
||||||
|
String groupSelfPrefix = null;
|
||||||
|
if (groupRawData.containsKey("prefix")) {
|
||||||
|
groupSelfPrefix = groupRawData.get("prefix").stream()
|
||||||
|
.map(e -> e.get(SQLPermissions.value))
|
||||||
|
.collect(Collectors.joining());
|
||||||
|
}
|
||||||
|
|
||||||
|
String groupSelfSuffix = null;
|
||||||
|
if (groupRawData.containsKey("suffix")) {
|
||||||
|
groupSelfSuffix = groupRawData.get("suffix").stream()
|
||||||
|
.map(e -> e.get(SQLPermissions.value))
|
||||||
|
.collect(Collectors.joining());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<ServerWorldKey, List<String>> groupSelfPerms = new LinkedHashMap<>();
|
||||||
|
if (groupRawData.containsKey("permissions")) {
|
||||||
|
groupSelfPerms = groupRawData.get("permissions").stream()
|
||||||
|
.peek(e -> {
|
||||||
|
String value = e.get(SQLPermissions.value);
|
||||||
|
newFullPermissionsList.add(value.substring(value.startsWith("-") ? 1 : 0));
|
||||||
|
})
|
||||||
|
.collect(Collectors.groupingBy(e -> new ServerWorldKey(e.get(SQLPermissions.server), e.get(SQLPermissions.world)),
|
||||||
|
LinkedHashMap::new,
|
||||||
|
Collectors.mapping(e -> e.get(SQLPermissions.value),
|
||||||
|
Collectors.toList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CachedGroup group = new CachedGroup(groupName, groupSelfPrefix, groupSelfSuffix, groupDefault, groupSelfPerms);
|
||||||
|
|
||||||
|
newData.put(groupName, group);
|
||||||
|
|
||||||
|
|
||||||
|
if (groupRawData.containsKey("inheritances")) {
|
||||||
|
groupRawData.get("inheritances").stream()
|
||||||
|
.map(e -> e.get(SQLPermissions.value))
|
||||||
|
.forEach(g -> {
|
||||||
|
initGroup(g, groupsRawData, newData, newFullPermissionsList);
|
||||||
|
group.inheritances.add(newData.get(g));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ static abstract class CachedEntity {
|
||||||
|
public final String name;
|
||||||
|
private final String selfPrefix, selfSuffix;
|
||||||
|
private final Map<ServerWorldKey, List<String>> selfPermissions;
|
||||||
|
|
||||||
|
private CachedEntity(String n, String p, String s,
|
||||||
|
Map<ServerWorldKey, List<String>> perms) {
|
||||||
|
name = n; selfPrefix = p; selfSuffix = s; selfPermissions = perms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ List<String> getSelfPermissions(String server, String world) {
|
||||||
|
return selfPermissions.getOrDefault(new ServerWorldKey(server, world), new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ Set<ServerWorldKey> getSelfPermissionsServerWorldKeys() {
|
||||||
|
return new TreeSet<>(selfPermissions.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ String getSelfPrefix() {
|
||||||
|
return selfPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ String getSelfSuffix() {
|
||||||
|
return selfSuffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ static class CachedPlayer extends CachedEntity {
|
||||||
|
public final UUID playerId;
|
||||||
|
public final List<CachedGroup> groups = new ArrayList<>();
|
||||||
|
public boolean usingDefaultGroups = false;
|
||||||
|
private CachedPlayer(UUID pl, String p, String s,
|
||||||
|
Map<ServerWorldKey, List<String>> perms) {
|
||||||
|
super(pl.toString(), p, s, perms);
|
||||||
|
playerId = pl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ static class CachedGroup extends CachedEntity {
|
||||||
|
public final boolean deflt;
|
||||||
|
public final List<CachedGroup> inheritances = new ArrayList<>();
|
||||||
|
private CachedGroup(String n, String p, String s,
|
||||||
|
boolean dflt, Map<ServerWorldKey, List<String>> perms) {
|
||||||
|
super(n, p, s, perms);
|
||||||
|
deflt = dflt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,545 @@
|
|||||||
|
package fr.pandacube.lib.core.permissions;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.cache.Cache;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.chat.Chat;
|
||||||
|
import fr.pandacube.lib.core.chat.ChatUtil;
|
||||||
|
import fr.pandacube.lib.core.chat.ChatUtil.DisplayTreeNode;
|
||||||
|
import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedEntity;
|
||||||
|
import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedGroup;
|
||||||
|
import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedPlayer;
|
||||||
|
import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType;
|
||||||
|
import fr.pandacube.lib.core.players.PlayerFinder;
|
||||||
|
import fr.pandacube.lib.core.util.Log;
|
||||||
|
import net.md_5.bungee.api.ChatColor;
|
||||||
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
|
|
||||||
|
public class PermissionsResolver {
|
||||||
|
|
||||||
|
private PermissionsCachedBackendReader backendReader;
|
||||||
|
|
||||||
|
/* package */ PermissionsResolver(PermissionsCachedBackendReader b) {
|
||||||
|
backendReader = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void clearPlayerFromCache(UUID player) {
|
||||||
|
String playerId = player.toString();
|
||||||
|
synchronized (effectivePermissionsCache) {
|
||||||
|
effectivePermissionsCache.asMap().keySet().removeIf(k -> k.type == EntityType.User && playerId.equals(k.name));
|
||||||
|
}
|
||||||
|
synchronized (effectiveDataCache) {
|
||||||
|
effectiveDataCache.asMap().keySet().removeIf(k -> k.type == EntityType.User && playerId.equals(k.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void clearCache() {
|
||||||
|
effectivePermissionsCache.invalidateAll();
|
||||||
|
effectiveDataCache.invalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ String getEffectivePrefix(String name, EntityType type) {
|
||||||
|
return getEffectiveData(name, type, DataType.PREFIX);
|
||||||
|
}
|
||||||
|
/* package */ String getEffectiveSuffix(String name, EntityType type) {
|
||||||
|
return getEffectiveData(name, type, DataType.SUFFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ DisplayTreeNode debugPrefix(String name, EntityType type) {
|
||||||
|
return debugData(name, type, DataType.PREFIX);
|
||||||
|
}
|
||||||
|
/* package */ DisplayTreeNode debugSuffix(String name, EntityType type) {
|
||||||
|
return debugData(name, type, DataType.SUFFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cache<DataCacheKey, String> effectiveDataCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterWrite(5, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private String getEffectiveData(String name, EntityType type, DataType dataType) {
|
||||||
|
Preconditions.checkNotNull(name, "name can’t be null");
|
||||||
|
Preconditions.checkNotNull(type, "type can’t be null");
|
||||||
|
|
||||||
|
try {
|
||||||
|
return effectiveDataCache.get(new DataCacheKey(name, type, dataType), () -> {
|
||||||
|
return resolveData(name, type, dataType);
|
||||||
|
});
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Log.severe(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DisplayTreeNode debugData(String name, EntityType type, DataType dataType) {
|
||||||
|
CachedEntity entity = (type == EntityType.User)
|
||||||
|
? backendReader.getCachedPlayer(UUID.fromString(name))
|
||||||
|
: backendReader.getCachedGroup(name);
|
||||||
|
return resolveData(entity, dataType).toDisplayTreeNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveData(String name, EntityType type, DataType dataType) {
|
||||||
|
CachedEntity entity = (type == EntityType.User)
|
||||||
|
? backendReader.getCachedPlayer(UUID.fromString(name))
|
||||||
|
: backendReader.getCachedGroup(name);
|
||||||
|
DataResolutionNode resolutionResult = resolveData(entity, dataType);
|
||||||
|
|
||||||
|
if (resolutionResult.conflict) {
|
||||||
|
Log.warning("For data " + dataType + ":");
|
||||||
|
BaseComponent[] cmps = ChatUtil.treeView(resolutionResult.toDisplayTreeNode(), true);
|
||||||
|
for (BaseComponent cmp : cmps)
|
||||||
|
Log.warning(cmp.toLegacyText());
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolutionResult.result != null ? resolutionResult.result : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataResolutionNode resolveData(CachedEntity entity, DataType dataType) {
|
||||||
|
// self data
|
||||||
|
DataResolutionNode resolutionNode = new DataResolutionNode(entity, dataType.getter.apply(entity), null);
|
||||||
|
if (resolutionNode.result != null) {
|
||||||
|
return resolutionNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check inheritances data
|
||||||
|
List<CachedGroup> inheritances = resolutionNode.entity instanceof CachedPlayer
|
||||||
|
? ((CachedPlayer)resolutionNode.entity).groups
|
||||||
|
: ((CachedGroup)resolutionNode.entity).inheritances;
|
||||||
|
|
||||||
|
List<DataResolutionNode> inheritedResults = new ArrayList<>(inheritances.size());
|
||||||
|
|
||||||
|
for (CachedGroup inherited : inheritances) {
|
||||||
|
inheritedResults.add(resolveData(inherited, dataType));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolutionNode.inheritances.addAll(inheritedResults);
|
||||||
|
|
||||||
|
if (inheritedResults.stream().anyMatch(g -> g.conflict))
|
||||||
|
resolutionNode.conflict = true;
|
||||||
|
|
||||||
|
Set<String> inheritedPermissions = inheritedResults.stream()
|
||||||
|
.map(g -> g.result)
|
||||||
|
.filter(r -> r != null)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
if (inheritedPermissions.size() == 1)
|
||||||
|
resolutionNode.result = inheritedPermissions.iterator().next();
|
||||||
|
else if (inheritedPermissions.size() > 1) {
|
||||||
|
resolutionNode.conflictMessage = (resolutionNode.conflictMessage == null ? "" : (resolutionNode.conflictMessage + " ; "))
|
||||||
|
+ "Unsolvable conflict between inherited groups";
|
||||||
|
resolutionNode.conflict = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolutionNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class DataResolutionNode {
|
||||||
|
final CachedEntity entity;
|
||||||
|
String result;
|
||||||
|
String conflictMessage;
|
||||||
|
boolean conflict;
|
||||||
|
final List<DataResolutionNode> inheritances = new ArrayList<>();
|
||||||
|
public DataResolutionNode(CachedEntity e, String r, String c) {
|
||||||
|
entity = e; result = r; conflictMessage = c;
|
||||||
|
conflict = c != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DisplayTreeNode toDisplayTreeNode() {
|
||||||
|
Chat c = Chat.text(entity.name);
|
||||||
|
if (result == null)
|
||||||
|
c.then(Chat.text(" (non défini)").gray());
|
||||||
|
else
|
||||||
|
c.thenLegacyText(" \"" + ChatColor.RESET + result + ChatColor.RESET + "\"");
|
||||||
|
if (conflictMessage != null)
|
||||||
|
c.thenFailure(" " + conflictMessage);
|
||||||
|
DisplayTreeNode node = new DisplayTreeNode(c.get());
|
||||||
|
|
||||||
|
if (result == null && conflict == false && !inheritances.isEmpty()) {
|
||||||
|
// there is nothing interesting to show on current or subnode
|
||||||
|
node.children.add(new DisplayTreeNode(Chat.text("(Inheritances hidden for brevety)").darkGray().italic().get()));
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
inheritances.forEach(n -> node.children.add(n.toDisplayTreeNode()));
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DataCacheKey {
|
||||||
|
final String name;
|
||||||
|
final EntityType type;
|
||||||
|
final DataType dataType;
|
||||||
|
DataCacheKey(String n, EntityType t, DataType d) {
|
||||||
|
name = n; type = t; dataType = d;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null || !(obj instanceof DataCacheKey))
|
||||||
|
return false;
|
||||||
|
DataCacheKey o = (DataCacheKey) obj;
|
||||||
|
return Objects.equals(name, o.name)
|
||||||
|
&& Objects.equals(type, o.type)
|
||||||
|
&& dataType == o.dataType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DataType {
|
||||||
|
PREFIX(CachedEntity::getSelfPrefix),
|
||||||
|
SUFFIX(CachedEntity::getSelfSuffix);
|
||||||
|
|
||||||
|
private final CachedEntityGetter<String> getter;
|
||||||
|
private DataType(CachedEntityGetter<String> g) {
|
||||||
|
getter = g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface CachedEntityGetter<R> {
|
||||||
|
R apply(CachedEntity a);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private Cache<PermCacheKey, PermState> effectivePermissionsCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterWrite(5, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
/* package */ Boolean getEffectivePermission(String name, EntityType type, String permission, String server, String world) {
|
||||||
|
Preconditions.checkNotNull(name, "name can’t be null");
|
||||||
|
Preconditions.checkNotNull(type, "type can’t be null");
|
||||||
|
Preconditions.checkNotNull(permission, "permission can’t be null");
|
||||||
|
Preconditions.checkArgument(world == null || server != null, "world not null but server is null");
|
||||||
|
|
||||||
|
boolean reversed = false;
|
||||||
|
if (permission.startsWith("-")) {
|
||||||
|
permission = permission.substring(1);
|
||||||
|
reversed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fPermission = permission == null ? null : permission.toLowerCase();
|
||||||
|
String fServer = server == null ? null : server.toLowerCase();
|
||||||
|
String fWorld = world == null ? null : world.toLowerCase();
|
||||||
|
try {
|
||||||
|
Boolean resolved = effectivePermissionsCache.get(new PermCacheKey(name, type, fPermission, fServer, fWorld), () -> {
|
||||||
|
return resolvePermission(name, type, fPermission, fServer, fWorld);
|
||||||
|
}).value;
|
||||||
|
return resolved == null ? null : (reversed != resolved.booleanValue());
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Log.severe(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ DisplayTreeNode debugPermission(String name, EntityType type, String permission, String server, String world) {
|
||||||
|
CachedEntity entity = (type == EntityType.User)
|
||||||
|
? backendReader.getCachedPlayer(UUID.fromString(name))
|
||||||
|
: backendReader.getCachedGroup(name);
|
||||||
|
return resolvePermission(entity, permission, server, world, true).toDisplayTreeNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PermState resolvePermission(String name, EntityType type, String permission, String server, String world) {
|
||||||
|
|
||||||
|
CachedEntity entity = (type == EntityType.User)
|
||||||
|
? backendReader.getCachedPlayer(UUID.fromString(name))
|
||||||
|
: backendReader.getCachedGroup(name);
|
||||||
|
PermResolutionNode resolutionResult = resolvePermission(entity, permission, server, world, true);
|
||||||
|
|
||||||
|
if (resolutionResult.conflict) {
|
||||||
|
Log.warning("For permission " + permission + ":");
|
||||||
|
BaseComponent[] cmps = ChatUtil.treeView(resolutionResult.toDisplayTreeNode(), true);
|
||||||
|
for (BaseComponent cmp : cmps)
|
||||||
|
Log.warning(cmp.toLegacyText());
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolutionResult.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private PermResolutionNode resolvePermission(CachedEntity entity, String permission, String server, String world, boolean checkInheritance) {
|
||||||
|
|
||||||
|
// self and special permissions
|
||||||
|
PermResolutionNode resolutionNode = resolveSelfPermission(entity, permission, server, world);
|
||||||
|
if (resolutionNode.result != PermState.UNDEFINED) {
|
||||||
|
return resolutionNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CachedGroup> inheritances = resolutionNode.entity instanceof CachedPlayer
|
||||||
|
? ((CachedPlayer)resolutionNode.entity).groups
|
||||||
|
: ((CachedGroup)resolutionNode.entity).inheritances;
|
||||||
|
|
||||||
|
|
||||||
|
// check no-world/no-server permissions
|
||||||
|
if (server != null) {
|
||||||
|
PermResolutionNode noWNoSNode = resolvePermission(entity, permission, world != null ? server : null, null, false);
|
||||||
|
resolutionNode.inheritances.add(noWNoSNode);
|
||||||
|
if (noWNoSNode.conflict)
|
||||||
|
resolutionNode.conflict = true;
|
||||||
|
if (noWNoSNode.result != PermState.UNDEFINED) {
|
||||||
|
resolutionNode.result = noWNoSNode.result;
|
||||||
|
return resolutionNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!checkInheritance)
|
||||||
|
return resolutionNode;
|
||||||
|
|
||||||
|
// check inheritances permissions
|
||||||
|
List<PermResolutionNode> inheritedResults = new ArrayList<>(inheritances.size() + 1);
|
||||||
|
|
||||||
|
for (CachedGroup inherited : inheritances) {
|
||||||
|
inheritedResults.add(resolvePermission(inherited, permission, server, world, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolutionNode.inheritances.addAll(inheritedResults);
|
||||||
|
|
||||||
|
if (inheritedResults.stream().anyMatch(g -> g.conflict))
|
||||||
|
resolutionNode.conflict = true;
|
||||||
|
|
||||||
|
Set<PermState> inheritedPermissions = inheritedResults.stream()
|
||||||
|
.map(g -> g.result)
|
||||||
|
.collect(Collectors.toCollection(() -> EnumSet.noneOf(PermState.class)));
|
||||||
|
|
||||||
|
boolean inheritancesGranted = inheritedPermissions.contains(PermState.GRANTED);
|
||||||
|
boolean inheritancesRevoqued = inheritedPermissions.contains(PermState.REVOQUED);
|
||||||
|
if (inheritancesGranted != inheritancesRevoqued) {
|
||||||
|
resolutionNode.result = inheritancesGranted ? PermState.GRANTED : PermState.REVOQUED;
|
||||||
|
}
|
||||||
|
else if (inheritancesGranted && inheritancesRevoqued) {
|
||||||
|
resolutionNode.conflictMessage = (resolutionNode.conflictMessage == null ? "" : (resolutionNode.conflictMessage + " ; "))
|
||||||
|
+ "Unsolvable conflict between inheritances";
|
||||||
|
resolutionNode.conflict = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolutionNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private PermResolutionNode resolveSelfPermission(CachedEntity entity, String permission, String server, String world) {
|
||||||
|
// special permissions
|
||||||
|
PermState result = PermState.UNDEFINED;
|
||||||
|
String conflict = null;
|
||||||
|
List<ParsedSelfPermission> foundPerms = null;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for special permissions
|
||||||
|
*/
|
||||||
|
if (entity instanceof CachedPlayer) {
|
||||||
|
PermPlayer permP = new PermPlayer(((CachedPlayer) entity).playerId);
|
||||||
|
ParsedSelfPermission specialPerm = null;
|
||||||
|
if (permission.equals("pandacube.grade.isinstaff")) {
|
||||||
|
boolean res = permP.inheritsFromGroup("staff-base", true);
|
||||||
|
specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL);
|
||||||
|
conflict = "Special permission 'pandacube.grade.isinstaff' is deprecated. Use 'pandacube.inheritsfrom.<staffBaseGroup>' instead.";
|
||||||
|
}
|
||||||
|
else if (permission.startsWith("pandacube.grade.")) {
|
||||||
|
String group = permission.substring("pandacube.grade.".length());
|
||||||
|
boolean res = permP.inheritsFromGroup(group, false);
|
||||||
|
specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL);
|
||||||
|
conflict = "Special permission 'pandacube.grade.<groupName>' is deprecated. Use 'pandacube.ingroup.<groupName>' instead.";
|
||||||
|
}
|
||||||
|
else if (permission.startsWith("pandacube.ingroup.")) {
|
||||||
|
String group = permission.substring("pandacube.ingroup.".length());
|
||||||
|
boolean res = permP.inheritsFromGroup(group, false);
|
||||||
|
specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL);
|
||||||
|
}
|
||||||
|
else if (permission.startsWith("pandacube.inheritsfrom.")) {
|
||||||
|
String group = permission.substring("pandacube.inheritsfrom.".length());
|
||||||
|
boolean res = permP.inheritsFromGroup(group, true);
|
||||||
|
specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL);
|
||||||
|
}
|
||||||
|
else if (permission.startsWith("pandacube.inserver.")) {
|
||||||
|
String testedServer = permission.substring("pandacube.inserver.".length());
|
||||||
|
boolean res = testedServer.equals(server);
|
||||||
|
specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL);
|
||||||
|
}
|
||||||
|
else if (permission.startsWith("pandacube.inserverworld.")) {
|
||||||
|
String testedServerWorld = permission.substring("pandacube.inserverworld.".length());
|
||||||
|
boolean res = server != null && world != null && testedServerWorld.equals(server + "." + world);
|
||||||
|
specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (specialPerm != null) {
|
||||||
|
result = PermState.of(specialPerm.result);
|
||||||
|
foundPerms = new ArrayList<>();
|
||||||
|
foundPerms.add(specialPerm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (result == PermState.UNDEFINED) {
|
||||||
|
foundPerms = entity.getSelfPermissions(server, world).stream()
|
||||||
|
.map(p -> {
|
||||||
|
ParsedSelfPermission resNode = null;
|
||||||
|
if (p.equalsIgnoreCase(permission))
|
||||||
|
resNode = new ParsedSelfPermission(p, true, PermType.EXPLICIT);
|
||||||
|
else if (p.equalsIgnoreCase("-" + permission))
|
||||||
|
resNode = new ParsedSelfPermission(p, false, PermType.EXPLICIT);
|
||||||
|
else if (p.endsWith("*") && permission.startsWith(p.substring(0, p.length() - 1)))
|
||||||
|
resNode = new ParsedSelfPermission(p, true, PermType.WILDCARD);
|
||||||
|
else if (p.endsWith("*") && p.startsWith("-") && permission.startsWith(p.substring(1, p.length() - 1)))
|
||||||
|
resNode = new ParsedSelfPermission(p, false, PermType.WILDCARD);
|
||||||
|
return resNode;
|
||||||
|
})
|
||||||
|
.filter(p -> p != null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
boolean explicitGranted = foundPerms.stream()
|
||||||
|
.anyMatch(n -> n.type == PermType.EXPLICIT && n.result == Boolean.TRUE);
|
||||||
|
boolean explicitRevoqued = foundPerms.stream()
|
||||||
|
.anyMatch(n -> n.type == PermType.EXPLICIT && n.result == Boolean.FALSE);
|
||||||
|
|
||||||
|
boolean wildcardGranted = foundPerms.stream()
|
||||||
|
.anyMatch(n -> n.type == PermType.WILDCARD && n.result == Boolean.TRUE);
|
||||||
|
boolean wildcardRevoqued = foundPerms.stream()
|
||||||
|
.anyMatch(n -> n.type == PermType.WILDCARD && n.result == Boolean.FALSE);
|
||||||
|
|
||||||
|
if (explicitGranted != explicitRevoqued) {
|
||||||
|
result = PermState.of(explicitGranted);
|
||||||
|
if (!wildcardGranted && !wildcardRevoqued) { }
|
||||||
|
else if (wildcardGranted && wildcardRevoqued) {
|
||||||
|
conflict = "Self explicit permission defined but conflict between self wildcard permissions";
|
||||||
|
}
|
||||||
|
else if (explicitGranted == wildcardGranted) {
|
||||||
|
conflict = "Unnecessary explicit permission already granted by self wildcard permissions"; // redundent explicit perm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (explicitGranted && explicitRevoqued) {
|
||||||
|
conflict = "Unsolvable conflit between explicit permissions";
|
||||||
|
}
|
||||||
|
else if (wildcardGranted != wildcardRevoqued) {
|
||||||
|
result = PermState.of(wildcardGranted);
|
||||||
|
}
|
||||||
|
else if (wildcardGranted && wildcardRevoqued) {
|
||||||
|
conflict = "Unsolvable conflit between wildcard permissions";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PermResolutionNode node = new PermResolutionNode(entity, server, world, result, conflict);
|
||||||
|
node.selfPermissions = foundPerms;
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PermResolutionNode {
|
||||||
|
final CachedEntity entity;
|
||||||
|
final String server, world;
|
||||||
|
PermState result;
|
||||||
|
String conflictMessage;
|
||||||
|
boolean conflict;
|
||||||
|
List<ParsedSelfPermission> selfPermissions = new ArrayList<>();
|
||||||
|
final List<PermResolutionNode> inheritances = new ArrayList<>();
|
||||||
|
public PermResolutionNode(CachedEntity e, String s, String w, PermState r, String c) {
|
||||||
|
entity = e; server = s; world = w; result = r; conflictMessage = c;
|
||||||
|
conflict = c != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DisplayTreeNode toDisplayTreeNode() {
|
||||||
|
Chat c = Chat.chat()
|
||||||
|
.then(result == PermState.UNDEFINED ? Chat.dataText("■") : result == PermState.GRANTED ? Chat.successText("✔") : Chat.failureText("✘"))
|
||||||
|
.then(Chat.text(entity instanceof CachedPlayer ? PlayerFinder.getLastKnownName(((CachedPlayer)entity).playerId) : entity.name)
|
||||||
|
.color(entity instanceof CachedPlayer ? ChatColor.GOLD : ChatColor.DARK_AQUA)
|
||||||
|
);
|
||||||
|
if (server != null)
|
||||||
|
c.thenData(" s=" + server);
|
||||||
|
if (world != null)
|
||||||
|
c.thenData(" w=" + world);
|
||||||
|
if (conflictMessage != null)
|
||||||
|
c.then(Chat.failureText(" " + conflictMessage));
|
||||||
|
DisplayTreeNode node = new DisplayTreeNode(c.get());
|
||||||
|
|
||||||
|
selfPermissions.forEach(p -> node.children.add(p.toDisplayTreeNode()));
|
||||||
|
|
||||||
|
if (result == PermState.UNDEFINED && conflict == false && !inheritances.isEmpty()) {
|
||||||
|
// there is nothing interesting to show on current or subnode
|
||||||
|
node.children.add(new DisplayTreeNode(Chat.text("(Inheritances hidden for brevety)").darkGray().italic().get()));
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
inheritances.forEach(n -> node.children.add(n.toDisplayTreeNode()));
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ParsedSelfPermission {
|
||||||
|
final String permission;
|
||||||
|
final boolean result;
|
||||||
|
final PermType type;
|
||||||
|
public ParsedSelfPermission(String p, boolean r, PermType t) {
|
||||||
|
permission = p;
|
||||||
|
result = r;
|
||||||
|
type = t;
|
||||||
|
}
|
||||||
|
public DisplayTreeNode toDisplayTreeNode() {
|
||||||
|
return new DisplayTreeNode(Chat.chat()
|
||||||
|
.then(result ? Chat.successText("✔") : Chat.failureText("✘"))
|
||||||
|
.then(Chat.text(permission).color(type == PermType.WILDCARD ? ChatColor.YELLOW : type == PermType.SPECIAL ? ChatColor.LIGHT_PURPLE : ChatColor.WHITE))
|
||||||
|
.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PermCacheKey {
|
||||||
|
final String name;
|
||||||
|
final EntityType type;
|
||||||
|
final String permission, server, world;
|
||||||
|
public PermCacheKey(String n, EntityType t, String p, String s, String w) {
|
||||||
|
name = n; type = t; permission = p; server = s; world = w;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null || !(obj instanceof PermCacheKey))
|
||||||
|
return false;
|
||||||
|
PermCacheKey o = (PermCacheKey) obj;
|
||||||
|
return Objects.equals(name, o.name)
|
||||||
|
&& Objects.equals(type, o.type)
|
||||||
|
&& Objects.equals(permission, o.permission)
|
||||||
|
&& Objects.equals(server, o.server)
|
||||||
|
&& Objects.equals(world, o.world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum PermType {
|
||||||
|
EXPLICIT, WILDCARD, SPECIAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum PermState {
|
||||||
|
GRANTED(true),
|
||||||
|
REVOQUED(false),
|
||||||
|
UNDEFINED(null);
|
||||||
|
final Boolean value;
|
||||||
|
private PermState(Boolean v) { value = v; }
|
||||||
|
private static PermState of(Boolean v) {
|
||||||
|
return v == null ? UNDEFINED : v ? GRANTED : REVOQUED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package fr.pandacube.lib.core.permissions;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.db.SQLElement;
|
||||||
|
import fr.pandacube.lib.core.db.SQLField;
|
||||||
|
|
||||||
|
public class SQLPermissions extends SQLElement<SQLPermissions> {
|
||||||
|
|
||||||
|
public SQLPermissions() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLPermissions(int id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String tableName() {
|
||||||
|
return "pandacube_permissions";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final SQLField<SQLPermissions, String> name = field(VARCHAR(64), false);
|
||||||
|
public static final SQLField<SQLPermissions, Integer> type = field(TINYINT, false);
|
||||||
|
public static final SQLField<SQLPermissions, String> key = field(VARCHAR(256), false);
|
||||||
|
public static final SQLField<SQLPermissions, String> value = field(VARCHAR(256), false);
|
||||||
|
public static final SQLField<SQLPermissions, String> server = field(VARCHAR(64), true);
|
||||||
|
public static final SQLField<SQLPermissions, String> world = field(VARCHAR(64), true);
|
||||||
|
|
||||||
|
|
||||||
|
public enum EntityType {
|
||||||
|
User,
|
||||||
|
Group;
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return ordinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EntityType getByCode(int code) {
|
||||||
|
if (code >= 0 && code < values().length)
|
||||||
|
return values()[code];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package fr.pandacube.lib.core.permissions;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class ServerWorldKey implements Comparable<ServerWorldKey> {
|
||||||
|
public final String server, world;
|
||||||
|
ServerWorldKey(String s, String w) {
|
||||||
|
server = s; world = w;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null || !(obj instanceof ServerWorldKey))
|
||||||
|
return false;
|
||||||
|
ServerWorldKey o = (ServerWorldKey) obj;
|
||||||
|
return Objects.equals(server, o.server)
|
||||||
|
&& Objects.equals(world, o.world);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(world, server);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int compareTo(ServerWorldKey o) {
|
||||||
|
Comparator<String> compStrNullFirst = Comparator.nullsFirst(String::compareToIgnoreCase);
|
||||||
|
return Comparator.comparing((ServerWorldKey k) -> k.server, compStrNullFirst)
|
||||||
|
.thenComparing(k -> k.world, compStrNullFirst)
|
||||||
|
.compare(this, o);
|
||||||
|
}
|
||||||
|
}
|
247
Core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java
Normal file
247
Core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
package fr.pandacube.lib.core.players;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.chat.ChatColorUtil;
|
||||||
|
import fr.pandacube.lib.core.db.DBException;
|
||||||
|
import fr.pandacube.lib.core.permissions.PermPlayer;
|
||||||
|
import fr.pandacube.lib.core.permissions.Permissions;
|
||||||
|
import fr.pandacube.lib.core.util.Log;
|
||||||
|
|
||||||
|
public interface IOffPlayer {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* General data and state
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the id of the player
|
||||||
|
*/
|
||||||
|
public abstract UUID getUniqueId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the last known player name of this player, or null if this player never joined the network.
|
||||||
|
*/
|
||||||
|
public default String getName() {
|
||||||
|
return PlayerFinder.getLastKnownName(getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate if this player is connected to the current node (server or proxy, depending on interface implementation)
|
||||||
|
* @return wether the player is online or not
|
||||||
|
*/
|
||||||
|
public abstract boolean isOnline();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Related class instances
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the online instance of this player, if any exists.
|
||||||
|
* May return itself if the current instance already represent an online player.
|
||||||
|
*/
|
||||||
|
public abstract IOnlinePlayer getOnlineInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the database entry of this player, or null if the player never joined the network.
|
||||||
|
* @throws DBException
|
||||||
|
*/
|
||||||
|
public default SQLPlayer getDbPlayer() throws DBException {
|
||||||
|
return SQLPlayer.getPlayerFromUUID(getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the permission instance of this player. This will never return null.
|
||||||
|
* @return the permission instance of this player
|
||||||
|
*/
|
||||||
|
public default PermPlayer getPermissionUser() {
|
||||||
|
return Permissions.getPlayer(getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Display name
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the player (if any), with eventual prefix and suffix depending on permission groups
|
||||||
|
* (and team for bukkit implementation)
|
||||||
|
* @return the display name of the player
|
||||||
|
*/
|
||||||
|
public abstract String getDisplayName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an updated display name of the user,
|
||||||
|
* generated using eventual permission’s prefix(es) and suffix(es) of the player,
|
||||||
|
* and with color codes translated to Minecraft’s native {@code §}.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public default String getDisplayNameFromPermissionSystem() {
|
||||||
|
PermPlayer permU = getPermissionUser();
|
||||||
|
return ChatColorUtil.translateAlternateColorCodes('&',
|
||||||
|
permU.getPrefix() + getName() + permU.getSuffix());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Permissions and groups
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this player has the specified permission.
|
||||||
|
* If the player is online, this will redirect the
|
||||||
|
* method call to the {@link IOnlinePlayer} instance,
|
||||||
|
* that MUST override this current method to avoid recussive
|
||||||
|
* loop.
|
||||||
|
* If the player is offline, it just call the Pandacube
|
||||||
|
* permission system.
|
||||||
|
* @param permission the permission node to test
|
||||||
|
* @return whether this player has the provided permission
|
||||||
|
*/
|
||||||
|
public default boolean hasPermission(String permission) {
|
||||||
|
IOnlinePlayer online = getOnlineInstance();
|
||||||
|
|
||||||
|
if (online != null)
|
||||||
|
return online.hasPermission(permission);
|
||||||
|
|
||||||
|
// at this point, the player is offline
|
||||||
|
Boolean res = getPermissionUser().hasPermission(permission);
|
||||||
|
return res != null ? res : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the this player is part of the specified group
|
||||||
|
*
|
||||||
|
* @param group the permissions group
|
||||||
|
* @return <i>true</i> if this player is part of the group,
|
||||||
|
* <i>false</i> otherwise
|
||||||
|
*/
|
||||||
|
public default boolean isInGroup(String group) {
|
||||||
|
return getPermissionUser().isInGroup(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this player is part of the staff, based on permission groups
|
||||||
|
*/
|
||||||
|
public default boolean isInStaff() {
|
||||||
|
return getPermissionUser().inheritsFromGroup("staff-base", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ignore
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this player have the right to ignore the provided player
|
||||||
|
* @param ignored the player that is potentially ignored by this player.
|
||||||
|
* If this parameter is null, this method returns false.
|
||||||
|
*/
|
||||||
|
public default boolean canIgnore(IOffPlayer ignored) {
|
||||||
|
if (ignored == null)
|
||||||
|
return false;
|
||||||
|
if (equals(ignored))
|
||||||
|
return false;
|
||||||
|
if (!isInStaff() && !ignored.isInStaff())
|
||||||
|
return true;
|
||||||
|
return hasPermission("pandacube.ignore.bypassfor." + ignored.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the provided player have the right to ignore this player
|
||||||
|
* @param ignorer the player that potentially ignore this player
|
||||||
|
* If this parameter is null, this method returns false.
|
||||||
|
* @implNote the default implementation just calls {@link #canIgnore(IOffPlayer) ignorer.canIgnore(this)}.
|
||||||
|
*/
|
||||||
|
public default boolean canBeIgnoredBy(IOffPlayer ignorer) {
|
||||||
|
if (ignorer == null)
|
||||||
|
return false;
|
||||||
|
return ignorer.canIgnore(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if this player ignore the provided player.
|
||||||
|
* @param ignored the player that is potentially ignored by this player.
|
||||||
|
* If this parameter is null, this method returns false.
|
||||||
|
* @return true if this player have to right to ignore the provided player and is actually ignoring him.
|
||||||
|
*/
|
||||||
|
public default boolean isIgnoring(IOffPlayer ignored) {
|
||||||
|
if (!canIgnore(ignored))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return SQLPlayerIgnore.isPlayerIgnoringPlayer(getUniqueId(), ignored.getUniqueId());
|
||||||
|
} catch (DBException e) {
|
||||||
|
Log.severe("Can't determine if a player ignore another player, because we can't access to the database", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the provided player ignore this player, taking into account the exception permissions.
|
||||||
|
* @param ignorer the player that potentially ignore this player
|
||||||
|
* If this parameter is null, this method returns false.
|
||||||
|
* @return true if the provided player have to right to ignore this player and is actually ignoring him.
|
||||||
|
* @implNote the default implementation just calls {@link #isIgnoring(IOffPlayer) ignorer.isIgnoring(this)}.
|
||||||
|
*/
|
||||||
|
public default boolean isIgnoredBy(IOffPlayer ignorer) {
|
||||||
|
return ignorer.isIgnoring(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Modération
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the time when the player will be unmuted, or null if the player is not muted.
|
||||||
|
* @return the timestamp in millisecond of when the player will be unmuted
|
||||||
|
*/
|
||||||
|
public default Long getMuteTimeout() {
|
||||||
|
try {
|
||||||
|
Long muteTimeout = getDbPlayer().get(SQLPlayer.muteTimeout);
|
||||||
|
if (muteTimeout == null || muteTimeout <= System.currentTimeMillis())
|
||||||
|
return null;
|
||||||
|
return muteTimeout;
|
||||||
|
} catch (DBException e) {
|
||||||
|
Log.severe(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the player is currently muted, meaning that they cannot communicate
|
||||||
|
* through the chat or private messages.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public default boolean isMuted() {
|
||||||
|
return getMuteTimeout() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,300 @@
|
|||||||
|
package fr.pandacube.lib.core.players;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.chat.Chat;
|
||||||
|
import fr.pandacube.lib.core.db.DBException;
|
||||||
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
|
|
||||||
|
public interface IOnlinePlayer extends IOffPlayer {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* General data and state
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current name of this player
|
||||||
|
* @implSpec The implementation is expected to call the environment API
|
||||||
|
* (Bukkit/Bungee) to get the name of the player.
|
||||||
|
*/
|
||||||
|
public abstract String getName();
|
||||||
|
|
||||||
|
public abstract String getServerName();
|
||||||
|
|
||||||
|
public abstract String getWorldName();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalStateException if the player was not found in the database (should never happen)
|
||||||
|
* @throws DBException if a database access error occurs
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public default SQLPlayer getDbPlayer() throws DBException {
|
||||||
|
SQLPlayer p = SQLPlayer.getPlayerFromUUID(getUniqueId());
|
||||||
|
if (p == null)
|
||||||
|
throw new IllegalStateException("The player was not found in the database: " + getUniqueId());
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Related class instances
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Permissions and groups
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this online player has the specified permission.
|
||||||
|
* @implSpec the implementation of this method must not directly or
|
||||||
|
* indirectly call the method {@link IOffPlayer#hasPermission(String)},
|
||||||
|
* or it may result in a {@link StackOverflowError}.
|
||||||
|
*/
|
||||||
|
public abstract boolean hasPermission(String permission);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Vanish
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract boolean isVanished();
|
||||||
|
|
||||||
|
public default boolean isVanishedFor(IOffPlayer other) {
|
||||||
|
if (!isVanished())
|
||||||
|
return false; // can see unvanished
|
||||||
|
|
||||||
|
if (getUniqueId().equals(other.getUniqueId()))
|
||||||
|
return false; // can see themself
|
||||||
|
|
||||||
|
if (!isInStaff() && other.isInStaff())
|
||||||
|
return false; // can see non-staff as a staff
|
||||||
|
|
||||||
|
if (other.hasPermission("pandacube.vanish.see." + getUniqueId()))
|
||||||
|
return false; // can see if has a specific permission
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sending packet and stuff to player
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the provided message in the player’s chat, if
|
||||||
|
* the chat is activated.
|
||||||
|
* @param message the message to display.
|
||||||
|
*/
|
||||||
|
public abstract void sendMessage(BaseComponent message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the provided message in the player’s chat, if
|
||||||
|
* the chat is activated
|
||||||
|
* @param message the message to display
|
||||||
|
*/
|
||||||
|
public default void sendMessage(Chat message) {
|
||||||
|
sendMessage(message.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the provided message in the player’s chat, if
|
||||||
|
* they allows to display CHAT messages
|
||||||
|
* @param message the message to display.
|
||||||
|
* @param sender the player causing the send of this message. Client side filtering may occur.
|
||||||
|
* May be null if we don’t want client filtering, but still consider the message as CHAT message.
|
||||||
|
* @implNote implementation of this method should not filter the send of the message, based on
|
||||||
|
* the sender. This parameter is only there to be transmitted to the client, so client side filtering can
|
||||||
|
* be processed.
|
||||||
|
*/
|
||||||
|
public abstract void sendMessage(BaseComponent message, UUID sender);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the provided message in the player’s chat, if
|
||||||
|
* they allows to display CHAT messages
|
||||||
|
* @param message the message to display
|
||||||
|
* @param sender the player causing the send of this message. Client side filtering may occur.
|
||||||
|
* May be null if we don’t want client filtering, but still consider the message as CHAT message.
|
||||||
|
*/
|
||||||
|
public default void sendMessage(Chat message, UUID sender) {
|
||||||
|
sendMessage(message.get(), sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the provided message in the player’s chat, if the chat is
|
||||||
|
* activated, prepended with the server prefix.
|
||||||
|
* @param message the message to display
|
||||||
|
*/
|
||||||
|
public default void sendPrefixedMessage(BaseComponent message) {
|
||||||
|
sendMessage(IPlayerManager.prefixedAndColored(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the provided message in the player’s chat, if the chat is
|
||||||
|
* activated, prepended with the server prefix.
|
||||||
|
* @param message the message to display
|
||||||
|
*/
|
||||||
|
public default void sendPrefixedMessage(Chat message) {
|
||||||
|
sendPrefixedMessage(message.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a title in the middle of the screen.
|
||||||
|
* @param title The big text
|
||||||
|
* @param subtitle The less big text
|
||||||
|
* @param fadeIn Fade in time in tick
|
||||||
|
* @param stay Stay time in tick
|
||||||
|
* @param fadeOut Fade out time in tick
|
||||||
|
*/
|
||||||
|
public abstract void sendTitle(BaseComponent title, BaseComponent subtitle, int fadeIn, int stay, int fadeOut);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a title in the middle of the screen.
|
||||||
|
* @param title The big text
|
||||||
|
* @param subtitle The less big text
|
||||||
|
* @param fadeIn Fade in time in tick
|
||||||
|
* @param stay Stay time in tick
|
||||||
|
* @param fadeOut Fade out time in tick
|
||||||
|
*/
|
||||||
|
public default void sendTitle(Chat title, Chat subtitle, int fadeIn, int stay, int fadeOut) {
|
||||||
|
sendTitle(title.get(), subtitle.get(), fadeIn, stay, fadeOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the server brand field in the debug menu (F3) of the player
|
||||||
|
* (third line in 1.15 debug screen). Supports ChatColor codes but no
|
||||||
|
* line break.
|
||||||
|
* @param brand the server brand to send to the client.
|
||||||
|
*/
|
||||||
|
public abstract void sendServerBrand(String brand);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Client options
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public abstract ClientOptions getClientOptions();
|
||||||
|
|
||||||
|
public interface ClientOptions {
|
||||||
|
|
||||||
|
public Locale getLocale();
|
||||||
|
|
||||||
|
public int getViewDistance();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasChatColorEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the client is configured to completely hide the chat to the
|
||||||
|
* player. When this is the case, nothing is displayed in the chat box,
|
||||||
|
* and the player can’t send any message or command.
|
||||||
|
* @implSpec if the value is unknown, it is assumed that the chat is
|
||||||
|
* fully visible.
|
||||||
|
*/
|
||||||
|
public boolean isChatHidden();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the client is configured to display the chat normally.
|
||||||
|
* When this is the case, chat messages and system messages are
|
||||||
|
* displayed in the chat box, and the player can send messages and
|
||||||
|
* commands.
|
||||||
|
* @implSpec if the value is unknown, it is assumed that the chat is
|
||||||
|
* fully visible.
|
||||||
|
*/
|
||||||
|
public boolean isChatFullyVisible();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the client is configured to only display system messages
|
||||||
|
* in the chat.
|
||||||
|
* When this is the case, chat messages are hidden but system messages
|
||||||
|
* are visible in the chat box, and the player can only send commands.
|
||||||
|
* @implSpec if the value is unknown, it is assumed that the chat is
|
||||||
|
* fully visible.
|
||||||
|
*/
|
||||||
|
public boolean isChatOnlyDisplayingSystemMessages();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the client has configured the main hand on the left.
|
||||||
|
* @implSpec if the value is unknown, it is assumed that the main hand
|
||||||
|
* is on the right.
|
||||||
|
*/
|
||||||
|
public boolean isLeftHanded();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the client has configured the main hand on the right.
|
||||||
|
* @implSpec if the value is unknown, it is assumed that the main hand
|
||||||
|
* is on the right.
|
||||||
|
*/
|
||||||
|
public boolean isRightHanded();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasSkinCapeEnabled();
|
||||||
|
|
||||||
|
public boolean hasSkinJacketEnabled();
|
||||||
|
|
||||||
|
public boolean hasSkinLeftSleeveEnabled();
|
||||||
|
|
||||||
|
public boolean hasSkinRightSleeveEnabled();
|
||||||
|
|
||||||
|
public boolean hasSkinLeftPantsEnabled();
|
||||||
|
|
||||||
|
public boolean hasSkinRightPantsEnabled();
|
||||||
|
|
||||||
|
public boolean hasSkinHatsEnabled();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the player can send chat messages or receive chat messages from
|
||||||
|
* other players, according to their client configuration.
|
||||||
|
* <br>
|
||||||
|
* Chat messages represent public communication between players. By default,
|
||||||
|
* it only include actual chat message. This method may be used in commands
|
||||||
|
* like /me, /afk or the login/logout broadcasted messages
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public default boolean canChat() {
|
||||||
|
return getClientOptions().isChatFullyVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,436 @@
|
|||||||
|
package fr.pandacube.lib.core.players;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.chat.Chat;
|
||||||
|
import fr.pandacube.lib.core.db.DB;
|
||||||
|
import fr.pandacube.lib.core.db.DBInitTableException;
|
||||||
|
import fr.pandacube.lib.core.util.Log;
|
||||||
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
|
|
||||||
|
public abstract class IPlayerManager<OP extends IOnlinePlayer, OF extends IOffPlayer> {
|
||||||
|
private static IPlayerManager<?, ?> instance;
|
||||||
|
public static synchronized IPlayerManager<?, ?> getInstance() { return instance; }
|
||||||
|
|
||||||
|
|
||||||
|
private Map<UUID, OP> onlinePlayers = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
|
private LoadingCache<UUID, OF> offlinePlayers = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||||
|
.build(CacheLoader.from(pId -> newOffPlayerInstance(pId)));
|
||||||
|
|
||||||
|
|
||||||
|
public IPlayerManager() throws DBInitTableException {
|
||||||
|
synchronized (IPlayerManager.class) {
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB.initTable(SQLPlayer.class);
|
||||||
|
DB.initTable(SQLPlayerNameHistory.class);
|
||||||
|
DB.initTable(SQLPlayerIgnore.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected void addPlayer(OP p) {
|
||||||
|
onlinePlayers.put(p.getUniqueId(), p);
|
||||||
|
offlinePlayers.invalidate(p.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OP removePlayer(UUID p) {
|
||||||
|
return onlinePlayers.remove(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OP get(UUID p) {
|
||||||
|
return onlinePlayers.get(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOnline(UUID p) {
|
||||||
|
return onlinePlayers.containsKey(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlayerCount() {
|
||||||
|
return onlinePlayers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OP> getAll() {
|
||||||
|
return new ArrayList<>(onlinePlayers.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OP> getAllNotVanished() {
|
||||||
|
List<OP> players = getAll();
|
||||||
|
players.removeIf(op -> op.isVanished());
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OF getOffline(UUID p) {
|
||||||
|
if (p == null)
|
||||||
|
return null;
|
||||||
|
OP online = get(p);
|
||||||
|
if (online != null) {
|
||||||
|
offlinePlayers.invalidate(p);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
OF ret = (OF) online;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
// if not online
|
||||||
|
try {
|
||||||
|
return offlinePlayers.get(p); // load and cache new instance if necessary
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.severe("Cannot cache Offline player instance", e);
|
||||||
|
return newOffPlayerInstance(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract OF newOffPlayerInstance(UUID p);
|
||||||
|
|
||||||
|
protected abstract void sendMessageToConsole(BaseComponent message);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static BaseComponent prefixedAndColored(BaseComponent message) {
|
||||||
|
return Chat.chat()
|
||||||
|
.color(Chat.getConfig().broadcastColor)
|
||||||
|
.then(Chat.getConfig().prefix.get())
|
||||||
|
.then(message)
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Message broadcasting
|
||||||
|
*/
|
||||||
|
|
||||||
|
// BaseComponent/Chat/String message
|
||||||
|
// boolean prefix
|
||||||
|
// boolean console = (permission == null)
|
||||||
|
// String permission = null
|
||||||
|
// UUID sourcePlayer = null
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to some or all players, and eventually to the console.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @param permission if not null, the message is only sent to player with this permission.
|
||||||
|
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
||||||
|
* If null, the message will be sent as a SYSTEM chat message.
|
||||||
|
* If not null, the message will be sent as a CHAT message, and will not be sent
|
||||||
|
* to players ignoring the provided player.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(BaseComponent message, boolean prefix, boolean console, String permission, UUID sourcePlayer) {
|
||||||
|
Preconditions.checkNotNull(message, "message cannot be null");
|
||||||
|
|
||||||
|
IOffPlayer oSourcePlayer = getInstance().getOffline(sourcePlayer);
|
||||||
|
|
||||||
|
if (prefix)
|
||||||
|
message = prefixedAndColored(message);
|
||||||
|
|
||||||
|
for (IOnlinePlayer op : getInstance().getAll()) {
|
||||||
|
if (permission != null && !(op.hasPermission(permission))) continue;
|
||||||
|
if (sourcePlayer != null && op.isIgnoring(oSourcePlayer))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (sourcePlayer != null) {
|
||||||
|
if (op.canIgnore(oSourcePlayer)) {
|
||||||
|
op.sendMessage(message, sourcePlayer); // CHAT message with UUID
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
op.sendMessage(message, null); // CHAT message without UUID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
op.sendMessage(message); // SYSTEM message
|
||||||
|
}
|
||||||
|
|
||||||
|
if (console)
|
||||||
|
getInstance().sendMessageToConsole(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to some or all players, and eventually to the console.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, boolean, String, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @param permission if not null, the message is only sent to player with this permission.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(BaseComponent message, boolean prefix, boolean console, String permission) {
|
||||||
|
broadcast(message, prefix, console, permission, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to all players, and eventually to the console.
|
||||||
|
* <p>
|
||||||
|
* This method does not restrict the reception of the message to a specific permission. If you
|
||||||
|
* want to specify a permission, use {@link #broadcast(BaseComponent, boolean, boolean, String, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
||||||
|
* If null, the message will be sent as a SYSTEM chat message.
|
||||||
|
* If not null, the message will be sent as a CHAT message, and will not be sent
|
||||||
|
* to players ignoring the provided player.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(BaseComponent message, boolean prefix, boolean console, UUID sourcePlayer) {
|
||||||
|
broadcast(message, prefix, console, null, sourcePlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to all players, and eventually to the console.
|
||||||
|
* <p>
|
||||||
|
* This method does not restrict the reception of the message to a specific permission. If you
|
||||||
|
* want to specify a permission, use {@link #broadcast(BaseComponent, boolean, boolean, String)}.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, boolean, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(BaseComponent message, boolean prefix, boolean console) {
|
||||||
|
broadcast(message, prefix, console, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to some or all players, and eventually to the console.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, String, UUID)}.
|
||||||
|
* <p>
|
||||||
|
* This method decides to send the message to the console depending on whether {@code permission}
|
||||||
|
* is null (will send to console) or not (will not send to console). To specify this behaviour, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, boolean, String)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param permission if not null, the message is only sent to player with this permission (but not to console).
|
||||||
|
* If null, the message will be sent to all players and to console.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(BaseComponent message, boolean prefix, String permission) {
|
||||||
|
broadcast(message, prefix, (permission == null), permission, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to all players, and to the console.
|
||||||
|
* <p>
|
||||||
|
* This method does not restrict the reception of the message to a specific permission. If you
|
||||||
|
* want to specify a permission, use {@link #broadcast(BaseComponent, boolean, String, UUID)}.
|
||||||
|
* <p>
|
||||||
|
* This method sends the message to the console. To change this behaviour, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, boolean, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
||||||
|
* If null, the message will be sent as a SYSTEM chat message.
|
||||||
|
* If not null, the message will be sent as a CHAT message, and will not be sent
|
||||||
|
* to players ignoring the provided player.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(BaseComponent message, boolean prefix, UUID sourcePlayer) {
|
||||||
|
broadcast(message, prefix, true, null, sourcePlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to all players, and to the console.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, UUID)}.
|
||||||
|
* <p>
|
||||||
|
* This method does not restrict the reception of the message to a specific permission. If you
|
||||||
|
* want to specify a permission, use {@link #broadcast(BaseComponent, boolean, String)}.
|
||||||
|
* <p>
|
||||||
|
* This method sends the message to the console. To change this behaviour, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, boolean)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(BaseComponent message, boolean prefix) {
|
||||||
|
broadcast(message, prefix, true, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to some or all players, and eventually to the console.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @param permission if not null, the message is only sent to player with this permission.
|
||||||
|
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
||||||
|
* If null, the message will be sent as a SYSTEM chat message.
|
||||||
|
* If not null, the message will be sent as a CHAT message, and will not be sent
|
||||||
|
* to players ignoring the provided player.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(Chat message, boolean prefix, boolean console, String permission, UUID sourcePlayer) {
|
||||||
|
Preconditions.checkNotNull(message, "message cannot be null");
|
||||||
|
broadcast(message.get(), prefix, console, permission, sourcePlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to some or all players, and eventually to the console.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, boolean, String, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @param permission if not null, the message is only sent to player with this permission.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(Chat message, boolean prefix, boolean console, String permission) {
|
||||||
|
broadcast(message, prefix, console, permission, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to all players, and eventually to the console.
|
||||||
|
* <p>
|
||||||
|
* This method does not restrict the reception of the message to a specific permission. If you
|
||||||
|
* want to specify a permission, use {@link #broadcast(BaseComponent, boolean, boolean, String, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
||||||
|
* If null, the message will be sent as a SYSTEM chat message.
|
||||||
|
* If not null, the message will be sent as a CHAT message, and will not be sent
|
||||||
|
* to players ignoring the provided player.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(Chat message, boolean prefix, boolean console, UUID sourcePlayer) {
|
||||||
|
broadcast(message, prefix, console, null, sourcePlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to all players, and eventually to the console.
|
||||||
|
* <p>
|
||||||
|
* This method does not restrict the reception of the message to a specific permission. If you
|
||||||
|
* want to specify a permission, use {@link #broadcast(BaseComponent, boolean, boolean, String)}.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, boolean, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(Chat message, boolean prefix, boolean console) {
|
||||||
|
broadcast(message, prefix, console, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to some or all players, and eventually to the console.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, String, UUID)}.
|
||||||
|
* <p>
|
||||||
|
* This method decides to send the message to the console depending on whether {@code permission}
|
||||||
|
* is null (will send to console) or not (will not send to console). To specify this behaviour, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, boolean, String)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param permission if not null, the message is only sent to player with this permission (but not to console).
|
||||||
|
* If null, the message will be sent to all players and to console.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(Chat message, boolean prefix, String permission) {
|
||||||
|
broadcast(message, prefix, (permission == null), permission, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to all players, and to the console.
|
||||||
|
* <p>
|
||||||
|
* This method does not restrict the reception of the message to a specific permission. If you
|
||||||
|
* want to specify a permission, use {@link #broadcast(BaseComponent, boolean, String, UUID)}.
|
||||||
|
* <p>
|
||||||
|
* This method sends the message to the console. To change this behaviour, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, boolean, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
||||||
|
* If null, the message will be sent as a SYSTEM chat message.
|
||||||
|
* If not null, the message will be sent as a CHAT message, and will not be sent
|
||||||
|
* to players ignoring the provided player.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(Chat message, boolean prefix, UUID sourcePlayer) {
|
||||||
|
broadcast(message, prefix, true, null, sourcePlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to all players, and to the console.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, UUID)}.
|
||||||
|
* <p>
|
||||||
|
* This method does not restrict the reception of the message to a specific permission. If you
|
||||||
|
* want to specify a permission, use {@link #broadcast(BaseComponent, boolean, String)}.
|
||||||
|
* <p>
|
||||||
|
* This method sends the message to the console. To change this behaviour, use
|
||||||
|
* {@link #broadcast(BaseComponent, boolean, boolean)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(Chat message, boolean prefix) {
|
||||||
|
broadcast(message, prefix, true, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,342 @@
|
|||||||
|
package fr.pandacube.lib.core.players;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.ToIntBiFunction;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.javatuples.Pair;
|
||||||
|
|
||||||
|
import com.google.common.cache.Cache;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.commands.SuggestionsSupplier;
|
||||||
|
import fr.pandacube.lib.core.db.DB;
|
||||||
|
import fr.pandacube.lib.core.db.DBException;
|
||||||
|
import fr.pandacube.lib.core.db.SQLOrderBy;
|
||||||
|
import fr.pandacube.lib.core.util.LevenshteinDistance;
|
||||||
|
import fr.pandacube.lib.core.util.Log;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Etape de recherche de joueur :
|
||||||
|
* utiliser directement la table pandacube_player
|
||||||
|
* chercher dans l'historique de login
|
||||||
|
*/
|
||||||
|
public class PlayerFinder {
|
||||||
|
|
||||||
|
private static Cache<UUID, String> playerLastKnownName = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterWrite(2, TimeUnit.MINUTES)
|
||||||
|
.maximumSize(1000)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static Cache<Pair<String, Boolean>, UUID> playerId = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterWrite(2, TimeUnit.MINUTES)
|
||||||
|
.maximumSize(1000)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static void clearCacheEntry(UUID pId, String pName) {
|
||||||
|
playerLastKnownName.invalidate(pId);
|
||||||
|
playerId.invalidate(Pair.with(pName.toLowerCase(), true));
|
||||||
|
playerId.invalidate(Pair.with(pName.toLowerCase(), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLastKnownName(UUID id) {
|
||||||
|
if (id == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return playerLastKnownName.get(id, () -> {
|
||||||
|
try {
|
||||||
|
return getDBPlayer(id).get(SQLPlayer.playerName); // eventual NPE will be ignored
|
||||||
|
} catch (NullPointerException|DBException e) {
|
||||||
|
Log.severe("Can't search for player name from uuid in database", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
// ignored (ORM Exception)
|
||||||
|
} catch (UncheckedExecutionException e) {
|
||||||
|
Log.severe("Can’t retrieve player last known name of " + id, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cherche un UUID de compte en se basant sur le pseudo passé en
|
||||||
|
* paramètre. La méthode
|
||||||
|
* cherchera d'abord dans les derniers pseudos connus. Puis, cherchera la
|
||||||
|
* dernière personne à
|
||||||
|
* s'être connecté avec ce pseudo sur le serveur.
|
||||||
|
*
|
||||||
|
* @param exactName le pseudo complet, insensible à la casse, et dans un
|
||||||
|
* format de pseudo valide
|
||||||
|
* @param old si on doit chercher dans les anciens pseudos de joueurs
|
||||||
|
* @return l'UUID du joueur si trouvé, null sinon
|
||||||
|
*/
|
||||||
|
public static UUID getPlayerId(String exactName, boolean old) {
|
||||||
|
if (!isValidPlayerName(exactName))
|
||||||
|
return null; // évite une recherche inutile dans la base de donnée
|
||||||
|
|
||||||
|
try {
|
||||||
|
return playerId.get(Pair.with(exactName.toLowerCase(), old), () -> {
|
||||||
|
try {
|
||||||
|
SQLPlayer el = DB.getFirst(SQLPlayer.class,
|
||||||
|
SQLPlayer.playerName.like(exactName.replace("_", "\\_")),
|
||||||
|
SQLOrderBy.desc(SQLPlayer.lastTimeInGame));
|
||||||
|
/*
|
||||||
|
* Si il n'y a pas 1 élément, alors soit le pseudo n'a jamais été attribué
|
||||||
|
* soit il a été changé, et nous avons l'ancien possesseur et le nouveau possesseur du pseudo.
|
||||||
|
*/
|
||||||
|
if (el != null)
|
||||||
|
return el.get(SQLPlayer.playerId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.severe("Can't search for uuid from player name in database", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old) {
|
||||||
|
try {
|
||||||
|
SQLPlayerNameHistory el = DB.getFirst(SQLPlayerNameHistory.class,
|
||||||
|
SQLPlayerNameHistory.playerName.like(exactName.replace("_", "\\_")),
|
||||||
|
SQLOrderBy.desc(SQLPlayerNameHistory.timeChanged));
|
||||||
|
if (el != null) return el.get(SQLPlayerNameHistory.playerId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.severe("Can't search for uuid from old player name in database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception(); // ignored
|
||||||
|
});
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a player name or a player ID from the provided string, and returns the UUID of the player, if found.
|
||||||
|
* @param nameOrId a valid player name, or a UUID in the format of {@link UUID#toString()}
|
||||||
|
* @return the id of the player, or null if not found or if the input is invalid.
|
||||||
|
*/
|
||||||
|
public static UUID parsePlayer(String nameOrId) {
|
||||||
|
if (nameOrId == null)
|
||||||
|
return null;
|
||||||
|
if (isValidPlayerName(nameOrId))
|
||||||
|
return getPlayerId(nameOrId, true);
|
||||||
|
try {
|
||||||
|
return UUID.fromString(nameOrId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidPlayerName(String name) {
|
||||||
|
if (name == null) return false;
|
||||||
|
return name.matches("[0-9a-zA-Z_]{2,16}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SQLPlayer getDBPlayer(UUID id) throws Exception {
|
||||||
|
if (id == null) return null;
|
||||||
|
return SQLPlayer.getPlayerFromUUID(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static final SuggestionsSupplier<?> TAB_PLAYER_OFFLINE = (sender, tokenIndex, token, args) -> {
|
||||||
|
if (token.length() < 3) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<SearchResponseProfile> list = findPlayer(token, 10).profiles;
|
||||||
|
if (!list.isEmpty() && list.get(0).d == 0)
|
||||||
|
return Collections.singletonList(list.get(0).name);
|
||||||
|
return list.stream().map(p -> p.name).collect(Collectors.toList());
|
||||||
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static final <S> SuggestionsSupplier<S> TAB_PLAYER_OFFLINE() {
|
||||||
|
return (SuggestionsSupplier<S>) TAB_PLAYER_OFFLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static SearchResponse findPlayer(String query, int resultsCount) {
|
||||||
|
SearchResponse cacheData = searchCache.getUnchecked(query.toLowerCase());
|
||||||
|
cacheData = new SearchResponse(cacheData.profiles.subList(0, Math.min(resultsCount, cacheData.profiles.size())));
|
||||||
|
return cacheData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int SEARCH_MAX_DISTANCE = 20;
|
||||||
|
public static int MISSING_CHAR_DISTANCE = 1;
|
||||||
|
public static int SURPLUS_CHAR_DISTANCE = 8;
|
||||||
|
public static int DIFF_CHAR_DISTANCE = 8;
|
||||||
|
public static int CLOSE_CHAR_DISTANCE = 4;
|
||||||
|
|
||||||
|
public static int OLD_NICK_MULTIPLIER = 2;
|
||||||
|
|
||||||
|
|
||||||
|
private static List<List<Character>> CONFUSABLE_CHARACTERS = ImmutableList.of(
|
||||||
|
ImmutableList.of('o', '0'),
|
||||||
|
ImmutableList.of('i', '1', 'l'),
|
||||||
|
ImmutableList.of('b', '8')
|
||||||
|
);
|
||||||
|
private static ToIntBiFunction<Character, Character> CHAR_DISTANCE = (c1, c2) -> {
|
||||||
|
if (c1.equals(c2))
|
||||||
|
return 0;
|
||||||
|
for (List<Character> charTab : CONFUSABLE_CHARACTERS) {
|
||||||
|
if (charTab.contains(c1) && charTab.contains(c2))
|
||||||
|
return CLOSE_CHAR_DISTANCE;
|
||||||
|
}
|
||||||
|
return DIFF_CHAR_DISTANCE;
|
||||||
|
};
|
||||||
|
|
||||||
|
private static LoadingCache<String, List<Pair<String, UUID>>> namesCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterWrite(2, TimeUnit.MINUTES)
|
||||||
|
.maximumSize(1)
|
||||||
|
.build(CacheLoader.from((String k) -> {
|
||||||
|
List<Pair<String, UUID>> cached = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
DB.forEach(SQLPlayerNameHistory.class, el -> {
|
||||||
|
cached.add(Pair.with(el.get(SQLPlayerNameHistory.playerName), el.get(SQLPlayerNameHistory.playerId)));
|
||||||
|
});
|
||||||
|
} catch (DBException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
|
}));
|
||||||
|
|
||||||
|
private static LoadingCache<String, SearchResponse> searchCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterWrite(2, TimeUnit.MINUTES)
|
||||||
|
.maximumSize(100)
|
||||||
|
.build(CacheLoader.from((String query) -> {
|
||||||
|
List<FoundName> foundNames = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
namesCache.get("").forEach(el -> {
|
||||||
|
String name = el.getValue0();
|
||||||
|
int dist = new LevenshteinDistance(name.toLowerCase(), query, SURPLUS_CHAR_DISTANCE, MISSING_CHAR_DISTANCE, CHAR_DISTANCE).getCurrentDistance();
|
||||||
|
if (dist <= SEARCH_MAX_DISTANCE) {
|
||||||
|
FoundName n = new FoundName();
|
||||||
|
n.dist = dist;
|
||||||
|
n.id = el.getValue1();
|
||||||
|
n.name = name;
|
||||||
|
foundNames.add(n);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<UUID, SearchResponseProfile> profiles = new HashMap<>();
|
||||||
|
|
||||||
|
foundNames.forEach(foundName -> {
|
||||||
|
SearchResponseProfile profile = profiles.getOrDefault(foundName.id, new SearchResponseProfile());
|
||||||
|
if (profile.id == null) {
|
||||||
|
profile.id = foundName.id.toString();
|
||||||
|
profile.names = new ArrayList<>();
|
||||||
|
profiles.put(foundName.id, profile);
|
||||||
|
}
|
||||||
|
profile.names.add(foundName);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB.forEach(SQLPlayer.class, SQLPlayer.playerId.in(profiles.keySet()), el -> {
|
||||||
|
SearchResponseProfile profile = profiles.get(el.get(SQLPlayer.playerId));
|
||||||
|
if (profile == null)
|
||||||
|
return;
|
||||||
|
profile.displayName = el.get(SQLPlayer.playerDisplayName);
|
||||||
|
profile.name = el.get(SQLPlayer.playerName);
|
||||||
|
FoundName currentName = null;
|
||||||
|
for (FoundName foundName : profile.names) {
|
||||||
|
if (foundName.name.equals(profile.name)) {
|
||||||
|
currentName = foundName;
|
||||||
|
profile.d = foundName.dist;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentName != null) {
|
||||||
|
profile.names.remove(currentName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int min = Integer.MAX_VALUE;
|
||||||
|
for (FoundName foundName : profile.names) {
|
||||||
|
if (foundName.dist < min) {
|
||||||
|
min = foundName.dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
profile.d = min * OLD_NICK_MULTIPLIER + 1;
|
||||||
|
|
||||||
|
if (profile.d > SEARCH_MAX_DISTANCE)
|
||||||
|
profiles.remove(el.get(SQLPlayer.playerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// unset id field in old names entries to save memory and network activity
|
||||||
|
profile.names.forEach(n -> n.id = null);
|
||||||
|
});
|
||||||
|
} catch (DBException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<SearchResponseProfile> searchResponseList = new ArrayList<>(profiles.values());
|
||||||
|
searchResponseList.sort(null);
|
||||||
|
|
||||||
|
searchResponseList.removeIf(p -> {
|
||||||
|
if (p.name == null) { // if the current name was not found in the database
|
||||||
|
Log.warning("Cannot find current name for player " + p.id, new Throwable());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return new SearchResponse(searchResponseList);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
public static class SearchResponseProfile implements Comparable<SearchResponseProfile> {
|
||||||
|
public int d;
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
public String displayName;
|
||||||
|
public List<FoundName> names;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(SearchResponseProfile o) {
|
||||||
|
return Integer.compare(d, o.d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FoundName {
|
||||||
|
public UUID id;
|
||||||
|
public String name;
|
||||||
|
public int dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static class SearchResponse {
|
||||||
|
public final List<SearchResponseProfile> profiles;
|
||||||
|
private SearchResponse(List<SearchResponseProfile> p) {
|
||||||
|
profiles = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package fr.pandacube.lib.core.players;
|
||||||
|
|
||||||
|
import java.sql.Date;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.db.DB;
|
||||||
|
import fr.pandacube.lib.core.db.DBException;
|
||||||
|
import fr.pandacube.lib.core.db.SQLElement;
|
||||||
|
import fr.pandacube.lib.core.db.SQLElementList;
|
||||||
|
import fr.pandacube.lib.core.db.SQLField;
|
||||||
|
|
||||||
|
public class SQLPlayer extends SQLElement<SQLPlayer> {
|
||||||
|
|
||||||
|
public SQLPlayer() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLPlayer(int id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nom de la table
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String tableName() {
|
||||||
|
return "pandacube_player";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Champs de la table
|
||||||
|
*/
|
||||||
|
public static final SQLField<SQLPlayer, UUID> playerId = field(CHAR36_UUID, false);
|
||||||
|
public static final SQLField<SQLPlayer, String> playerName = field(VARCHAR(16), false);
|
||||||
|
public static final SQLField<SQLPlayer, UUID> token = field(CHAR36_UUID, true);
|
||||||
|
public static final SQLField<SQLPlayer, String> mailCheck = field(VARCHAR(255), true);
|
||||||
|
public static final SQLField<SQLPlayer, String> password = field(VARCHAR(255), true);
|
||||||
|
public static final SQLField<SQLPlayer, String> mail = field(VARCHAR(255), true);
|
||||||
|
public static final SQLField<SQLPlayer, String> playerDisplayName = field(VARCHAR(255),
|
||||||
|
false);
|
||||||
|
public static final SQLField<SQLPlayer, Long> firstTimeInGame = field(BIGINT, false, 0L);
|
||||||
|
public static final SQLField<SQLPlayer, Long> timeWebRegister = field(BIGINT, true);
|
||||||
|
public static final SQLField<SQLPlayer, Long> lastTimeInGame = field(BIGINT, true);
|
||||||
|
public static final SQLField<SQLPlayer, Long> lastWebActivity = field(BIGINT, false, 0L);
|
||||||
|
public static final SQLField<SQLPlayer, String> onlineInServer = field(VARCHAR(32), true);
|
||||||
|
public static final SQLField<SQLPlayer, String> skinURL = field(VARCHAR(255), true);
|
||||||
|
public static final SQLField<SQLPlayer, Boolean> isVanish = field(BOOLEAN, false,
|
||||||
|
(Boolean) false);
|
||||||
|
public static final SQLField<SQLPlayer, Date> birthday = field(DATE, true);
|
||||||
|
public static final SQLField<SQLPlayer, Integer> lastYearCelebratedBirthday = field(INT,
|
||||||
|
false, 0);
|
||||||
|
public static final SQLField<SQLPlayer, Long> banTimeout = field(BIGINT, true);
|
||||||
|
public static final SQLField<SQLPlayer, Long> muteTimeout = field(BIGINT, true);
|
||||||
|
public static final SQLField<SQLPlayer, Boolean> isWhitelisted = field(BOOLEAN, false,
|
||||||
|
(Boolean) false);
|
||||||
|
public static final SQLField<SQLPlayer, Long> bambou = field(BIGINT, false, 0L);
|
||||||
|
public static final SQLField<SQLPlayer, String> grade = field(VARCHAR(36), false, "default");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static SQLPlayer getPlayerFromUUID(UUID pId) throws DBException {
|
||||||
|
if (pId == null)
|
||||||
|
return null;
|
||||||
|
return DB.getFirst(SQLPlayer.class, playerId.eq(pId));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static SQLElementList<SQLPlayer> getPlayersFromUUIDs(Set<UUID> playerIds) throws DBException {
|
||||||
|
|
||||||
|
if (playerIds == null || playerIds.isEmpty()) {
|
||||||
|
return new SQLElementList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return DB.getAll(SQLPlayer.class, playerId.in(playerIds));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package fr.pandacube.lib.core.players;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.db.DB;
|
||||||
|
import fr.pandacube.lib.core.db.DBException;
|
||||||
|
import fr.pandacube.lib.core.db.SQLElement;
|
||||||
|
import fr.pandacube.lib.core.db.SQLFKField;
|
||||||
|
|
||||||
|
public class SQLPlayerIgnore extends SQLElement<SQLPlayerIgnore> {
|
||||||
|
|
||||||
|
public SQLPlayerIgnore() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLPlayerIgnore(int id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String tableName() {
|
||||||
|
return "pandacube_player_ignore";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final SQLFKField<SQLPlayerIgnore, UUID, SQLPlayer> ignorer = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId);
|
||||||
|
public static final SQLFKField<SQLPlayerIgnore, UUID, SQLPlayer> ignored = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId);
|
||||||
|
|
||||||
|
|
||||||
|
public static SQLPlayerIgnore getPlayerIgnoringPlayer(UUID ignorer, UUID ignored) throws DBException {
|
||||||
|
return DB.getFirst(SQLPlayerIgnore.class, SQLPlayerIgnore.ignorer.eq(ignorer).and(SQLPlayerIgnore.ignored.eq(ignored)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPlayerIgnoringPlayer(UUID ignorer, UUID ignored) throws DBException {
|
||||||
|
return getPlayerIgnoringPlayer(ignorer, ignored) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setPlayerIgnorePlayer(UUID ignorer, UUID ignored, boolean newIgnoreState) throws DBException {
|
||||||
|
SQLPlayerIgnore el = getPlayerIgnoringPlayer(ignorer, ignored);
|
||||||
|
if (el == null && newIgnoreState) {
|
||||||
|
el = new SQLPlayerIgnore();
|
||||||
|
el.set(SQLPlayerIgnore.ignorer, ignorer);
|
||||||
|
el.set(SQLPlayerIgnore.ignored, ignored);
|
||||||
|
el.save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (el != null && !newIgnoreState) {
|
||||||
|
el.delete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<UUID, SQLPlayer> getIgnoredPlayer(UUID ignorer) throws DBException {
|
||||||
|
return DB.getAll(SQLPlayerIgnore.class, SQLPlayerIgnore.ignorer.eq(ignorer))
|
||||||
|
.getReferencedEntriesInGroups(SQLPlayerIgnore.ignored);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package fr.pandacube.lib.core.players;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.core.db.DB;
|
||||||
|
import fr.pandacube.lib.core.db.DBException;
|
||||||
|
import fr.pandacube.lib.core.db.SQLElement;
|
||||||
|
import fr.pandacube.lib.core.db.SQLFKField;
|
||||||
|
import fr.pandacube.lib.core.db.SQLField;
|
||||||
|
import fr.pandacube.lib.core.util.Log;
|
||||||
|
|
||||||
|
public class SQLPlayerNameHistory extends SQLElement<SQLPlayerNameHistory> {
|
||||||
|
|
||||||
|
public SQLPlayerNameHistory() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLPlayerNameHistory(int id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String tableName() {
|
||||||
|
return "pandacube_player_name_history";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final SQLFKField<SQLPlayerNameHistory, UUID, SQLPlayer> playerId = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId);
|
||||||
|
public static final SQLField<SQLPlayerNameHistory, String> playerName = field(VARCHAR(16), false);
|
||||||
|
public static final SQLField<SQLPlayerNameHistory, Long> timeChanged = field(BIGINT, true);
|
||||||
|
|
||||||
|
|
||||||
|
public static void updateIfNeeded(UUID player, String name, long time) {
|
||||||
|
SQLPlayerNameHistory histEl;
|
||||||
|
try {
|
||||||
|
histEl = DB.getFirst(SQLPlayerNameHistory.class, playerId.eq(player).and(playerName.eq(name)));
|
||||||
|
|
||||||
|
if (histEl == null) {
|
||||||
|
histEl = new SQLPlayerNameHistory();
|
||||||
|
histEl.set(playerId, player);
|
||||||
|
histEl.set(playerName, name);
|
||||||
|
histEl.set(timeChanged, time);
|
||||||
|
histEl.save();
|
||||||
|
}
|
||||||
|
else if (time < histEl.get(timeChanged)) {
|
||||||
|
histEl.set(timeChanged, time);
|
||||||
|
histEl.save();
|
||||||
|
}
|
||||||
|
} catch (DBException e) {
|
||||||
|
Log.severe(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.search;
|
package fr.pandacube.lib.core.search;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.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
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.search;
|
package fr.pandacube.lib.core.search;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface Callback<T> {
|
public interface Callback<T> {
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.measurement;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
public class EnumUtil {
|
public class EnumUtil {
|
||||||
|
|
32
Core/src/main/java/fr/pandacube/lib/core/util/FileUtils.java
Normal file
32
Core/src/main/java/fr/pandacube/lib/core/util/FileUtils.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
|
public class FileUtils {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static void delete(File target) {
|
||||||
|
if (target.isDirectory())
|
||||||
|
for (File child : target.listFiles())
|
||||||
|
delete(child);
|
||||||
|
target.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void copy(File source, File target) throws IOException {
|
||||||
|
if (target.exists() && !target.isDirectory()) {
|
||||||
|
throw new IllegalStateException("target file already exists: " + target);
|
||||||
|
}
|
||||||
|
if (source.isDirectory()) {
|
||||||
|
target.mkdir();
|
||||||
|
for (String child : source.list())
|
||||||
|
copy(new File(source, child), new File(target, child));
|
||||||
|
}
|
||||||
|
else if (source.isFile()) {
|
||||||
|
Files.copy(source.toPath(), target.toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
//******************************************************************************
|
//******************************************************************************
|
||||||
//***
|
//***
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.ToIntBiFunction;
|
import java.util.function.ToIntBiFunction;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.util.AbstractList;
|
import java.util.AbstractList;
|
||||||
import java.util.List;
|
import java.util.List;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.measurement;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
@ -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";
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.measurement;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
public class Tick {
|
public class Tick {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.util.measurement;
|
package fr.pandacube.lib.core.util;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
@ -1,15 +0,0 @@
|
|||||||
package fr.pandacube.util;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class DirUtils {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void delete(final File target) {
|
|
||||||
if (target.isDirectory())
|
|
||||||
for (File child : target.listFiles())
|
|
||||||
delete(child);
|
|
||||||
target.delete();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package fr.pandacube.util.orm;
|
|
||||||
|
|
||||||
public class ORMException extends Exception {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
public ORMException(Throwable initCause) {
|
|
||||||
super(initCause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ORMException(String message, Throwable initCause) {
|
|
||||||
super(message, initCause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ORMException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package fr.pandacube.util.orm;
|
|
||||||
|
|
||||||
public class ORMInitTableException extends ORMException {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
/* package */ <E extends SQLElement<E>> ORMInitTableException(Class<E> tableElem) {
|
|
||||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* package */ <E extends SQLElement<E>> ORMInitTableException(Class<E> tableElem, Throwable t) {
|
|
||||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"), t);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package fr.pandacube.util.orm;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.javatuples.Pair;
|
|
||||||
|
|
||||||
import fr.pandacube.util.Log;
|
|
||||||
|
|
||||||
public abstract class SQLWhere<E extends SQLElement<E>> {
|
|
||||||
|
|
||||||
public abstract Pair<String, List<Object>> toSQL() throws ORMException;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
try {
|
|
||||||
return toSQL().getValue0();
|
|
||||||
} catch (ORMException e) {
|
|
||||||
Log.warning(e);
|
|
||||||
return "[SQLWhere.toString() error (see logs)]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SQLWhereAnd<E> and(SQLWhere<E> other) {
|
|
||||||
return new SQLWhereAnd<E>().and(this).and(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SQLWhereOr<E> or(SQLWhere<E> other) {
|
|
||||||
return new SQLWhereOr<E>().or(this).or(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <E extends SQLElement<E>> SQLWhereAnd<E> and() {
|
|
||||||
return new SQLWhereAnd<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <E extends SQLElement<E>> SQLWhereOr<E> or() {
|
|
||||||
return new SQLWhereOr<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String escapeLike(String str) {
|
|
||||||
return str.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package fr.pandacube.util.orm;
|
|
||||||
|
|
||||||
public class SQLWhereAnd<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
|
||||||
|
|
||||||
/* package */ SQLWhereAnd() {
|
|
||||||
super(SQLBoolOp.AND);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SQLWhereAnd<E> and(SQLWhere<E> other) {
|
|
||||||
add(other);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package fr.pandacube.util.orm;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.javatuples.Pair;
|
|
||||||
|
|
||||||
public abstract class SQLWhereChain<E extends SQLElement<E>> extends SQLWhere<E> {
|
|
||||||
|
|
||||||
private SQLBoolOp operator;
|
|
||||||
protected List<SQLWhere<E>> conditions = new ArrayList<>();
|
|
||||||
|
|
||||||
/* package */ SQLWhereChain(SQLBoolOp op) {
|
|
||||||
if (op == null) throw new IllegalArgumentException("op can't be null");
|
|
||||||
operator = op;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void add(SQLWhere<E> sqlWhere) {
|
|
||||||
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
|
|
||||||
conditions.add(sqlWhere);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<String, List<Object>> toSQL() throws ORMException {
|
|
||||||
if (conditions.isEmpty()) {
|
|
||||||
throw new ORMException("SQLWhereChain needs at least one element inside !");
|
|
||||||
}
|
|
||||||
|
|
||||||
String sql = "";
|
|
||||||
List<Object> params = new ArrayList<>();
|
|
||||||
boolean first = true;
|
|
||||||
|
|
||||||
for (SQLWhere<E> w : conditions) {
|
|
||||||
if (!first) sql += " " + operator.sql + " ";
|
|
||||||
first = false;
|
|
||||||
|
|
||||||
Pair<String, List<Object>> ret = w.toSQL();
|
|
||||||
sql += "(" + ret.getValue0() + ")";
|
|
||||||
params.addAll(ret.getValue1());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Pair<>(sql, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* package */ enum SQLBoolOp {
|
|
||||||
/** Equivalent to SQL "<code>AND</code>" */
|
|
||||||
AND("AND"),
|
|
||||||
/** Equivalent to SQL "<code>OR</code>" */
|
|
||||||
OR("OR");
|
|
||||||
/* package */ final String sql;
|
|
||||||
|
|
||||||
private SQLBoolOp(String s) {
|
|
||||||
sql = s;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package fr.pandacube.util.orm;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.javatuples.Pair;
|
|
||||||
|
|
||||||
/* package */ class SQLWhereComp<E extends SQLElement<E>> extends SQLWhere<E> {
|
|
||||||
|
|
||||||
private SQLField<E, ?> left;
|
|
||||||
private SQLComparator comp;
|
|
||||||
private Object right;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare a field with a value
|
|
||||||
*
|
|
||||||
* @param l the field at left of the comparison operator. Can't be null
|
|
||||||
* @param c the comparison operator, can't be null
|
|
||||||
* @param r the value at right of the comparison operator. Can't be null
|
|
||||||
*/
|
|
||||||
/* package */ <T> SQLWhereComp(SQLField<E, T> l, SQLComparator c, T r) {
|
|
||||||
if (l == null || r == null || c == null)
|
|
||||||
throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null");
|
|
||||||
left = l;
|
|
||||||
comp = c;
|
|
||||||
right = r;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<String, List<Object>> toSQL() throws ORMException {
|
|
||||||
List<Object> params = new ArrayList<>();
|
|
||||||
SQLElement.addValueToSQLObjectList(params, left, right);
|
|
||||||
return new Pair<>("`" + left.getName() + "` " + comp.sql + " ? ", params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* package */ enum SQLComparator {
|
|
||||||
/** Equivalent to SQL "<code>=</code>" */
|
|
||||||
EQ("="),
|
|
||||||
/** Equivalent to SQL "<code>></code>" */
|
|
||||||
GT(">"),
|
|
||||||
/** Equivalent to SQL "<code>>=</code>" */
|
|
||||||
GEQ(">="),
|
|
||||||
/** Equivalent to SQL "<code><</code>" */
|
|
||||||
LT("<"),
|
|
||||||
/** Equivalent to SQL "<code><=</code>" */
|
|
||||||
LEQ("<="),
|
|
||||||
/** Equivalent to SQL "<code>!=</code>" */
|
|
||||||
NEQ("!=");
|
|
||||||
|
|
||||||
/* package */ final String sql;
|
|
||||||
|
|
||||||
private SQLComparator(String s) {
|
|
||||||
sql = s;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package fr.pandacube.util.orm;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.javatuples.Pair;
|
|
||||||
|
|
||||||
/* package */ class SQLWhereIn<E extends SQLElement<E>> extends SQLWhere<E> {
|
|
||||||
|
|
||||||
private SQLField<E, ?> field;
|
|
||||||
private Collection<?> values;
|
|
||||||
|
|
||||||
/* package */ <T> SQLWhereIn(SQLField<E, T> f, Collection<T> v) {
|
|
||||||
if (f == null || v == null)
|
|
||||||
throw new IllegalArgumentException("All arguments for SQLWhereIn constructor can't be null");
|
|
||||||
field = f;
|
|
||||||
values = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<String, List<Object>> toSQL() throws ORMException {
|
|
||||||
List<Object> params = new ArrayList<>();
|
|
||||||
|
|
||||||
if (values.isEmpty())
|
|
||||||
return new Pair<>(" 1=0 ", params);
|
|
||||||
|
|
||||||
for (Object v : values)
|
|
||||||
SQLElement.addValueToSQLObjectList(params, field, v);
|
|
||||||
|
|
||||||
char[] questions = new char[values.size() == 0 ? 0 : (values.size() * 2 - 1)];
|
|
||||||
for (int i = 0; i < questions.length; i++)
|
|
||||||
questions[i] = i % 2 == 0 ? '?' : ',';
|
|
||||||
|
|
||||||
return new Pair<>("`" + field.getName() + "` IN (" + new String(questions) + ") ", params);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package fr.pandacube.util.orm;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.javatuples.Pair;
|
|
||||||
|
|
||||||
/* package */ class SQLWhereLike<E extends SQLElement<E>> extends SQLWhere<E> {
|
|
||||||
|
|
||||||
private SQLField<E, ?> field;
|
|
||||||
private String likeExpr;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare a field with a value
|
|
||||||
*
|
|
||||||
* @param f the field at left of the LIKE keyword. Can't be null
|
|
||||||
* @param like the like expression.
|
|
||||||
*/
|
|
||||||
/* package */ SQLWhereLike(SQLField<E, ?> f, String like) {
|
|
||||||
if (f == null || like == null)
|
|
||||||
throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null");
|
|
||||||
field = f;
|
|
||||||
likeExpr = like;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<String, List<Object>> toSQL() {
|
|
||||||
ArrayList<Object> params = new ArrayList<>();
|
|
||||||
params.add(likeExpr);
|
|
||||||
return new Pair<>("`" + field.getName() + "` LIKE ? ", params);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package fr.pandacube.util.orm;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import org.javatuples.Pair;
|
|
||||||
|
|
||||||
import fr.pandacube.util.Log;
|
|
||||||
|
|
||||||
/* package */ class SQLWhereNull<E extends SQLElement<E>> extends SQLWhere<E> {
|
|
||||||
|
|
||||||
private SQLField<E, ?> fild;
|
|
||||||
private boolean nulll;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition.
|
|
||||||
*
|
|
||||||
* @param field the field to check null / not null state
|
|
||||||
* @param isNull true if we want to ckeck if "IS NULL", or false to check if
|
|
||||||
* "IS NOT NULL"
|
|
||||||
*/
|
|
||||||
/* package */ SQLWhereNull(SQLField<E, ?> field, boolean isNull) {
|
|
||||||
if (field == null) throw new IllegalArgumentException("field can't be null");
|
|
||||||
if (!field.canBeNull) Log.getLogger().log(Level.WARNING,
|
|
||||||
"Useless : Trying to check IS [NOT] NULL on the field " + field.getSQLElementType().getName() + "#"
|
|
||||||
+ field.getName() + " which is declared in the ORM as 'can't be null'");
|
|
||||||
fild = field;
|
|
||||||
nulll = isNull;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<String, List<Object>> toSQL() {
|
|
||||||
return new Pair<>("`" + fild.getName() + "` IS " + ((nulll) ? "NULL" : "NOT NULL"), new ArrayList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package fr.pandacube.util.orm;
|
|
||||||
|
|
||||||
public class SQLWhereOr<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
|
||||||
|
|
||||||
/* package */ SQLWhereOr() {
|
|
||||||
super(SQLBoolOp.OR);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SQLWhereOr<E> or(SQLWhere<E> other) {
|
|
||||||
add(other);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
20
Paper/.classpath
Normal file
20
Paper/.classpath
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
1
Paper/.gitignore
vendored
Normal file
1
Paper/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target/
|
23
Paper/.project
Normal file
23
Paper/.project
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>pandalib-paper</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user