Big refactor of Pandalib. More modules and better managed dependencies

This commit is contained in:
2022-07-20 13:18:57 +02:00
parent 1aec628b19
commit 7dcd92f72d
184 changed files with 1000 additions and 1986 deletions

View File

@@ -11,8 +11,6 @@
<artifactId>pandalib-core</artifactId>
<packaging>jar</packaging>
<name>PandaLib-Core</name>
<repositories>
<repository>
@@ -27,34 +25,42 @@
<dependencies>
<dependency>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-chat</artifactId>
<version>${bungeecord.version}</version>
<scope>compile</scope>
<groupId>fr.pandacube.lib</groupId>
<artifactId>pandalib-util</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-api</artifactId>
<version>4.11.0</version>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-platform-bungeecord</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-plain</artifactId>
<version>4.11.0</version>
</dependency>
<groupId>fr.pandacube.lib</groupId>
<artifactId>pandalib-chat</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.147</version>
</dependency>
<groupId>fr.pandacube.lib</groupId>
<artifactId>pandalib-db</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>fr.pandacube.lib</groupId>
<artifactId>pandalib-reflect</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>fr.pandacube.lib</groupId>
<artifactId>pandalib-permissions</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>fr.pandacube.lib</groupId>
<artifactId>pandalib-network-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.fathzer</groupId>
@@ -67,33 +73,6 @@
<groupId>org.geysermc.floodgate</groupId>
<artifactId>api</artifactId>
<version>2.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<include>io.github.classgraph:classgraph</include>
</includes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,458 +0,0 @@
package fr.pandacube.lib.core.chat;
import java.awt.Color;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import org.checkerframework.checker.nullness.qual.NonNull;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.BlockNBTComponent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.EntityNBTComponent;
import net.kyori.adventure.text.KeybindComponent;
import net.kyori.adventure.text.ScoreComponent;
import net.kyori.adventure.text.SelectorComponent;
import net.kyori.adventure.text.StorageNBTComponent;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEventSource;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.format.TextDecoration.State;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
public abstract sealed class Chat extends ChatStatic implements HoverEventSource<Component>, ComponentLike {
protected final ComponentBuilder<?, ?> builder;
protected boolean console = false;
/* package */ Chat(ComponentBuilder<?, ?> b) {
Objects.requireNonNull(b, "Provided component builder must not be null");
builder = b;
}
public Component getAdv() {
return builder.build();
}
public BaseComponent get() {
return toBungee(getAdv());
}
public BaseComponent[] getAsArray() {
return toBungeeArray(getAdv());
}
private static final LegacyComponentSerializer LEGACY_SERIALIZER_BUNGEE_FIENDLY = LegacyComponentSerializer.builder()
.hexColors()
.useUnusualXRepeatedCharacterHexFormat()
.build();
public String getLegacyText() {
return LEGACY_SERIALIZER_BUNGEE_FIENDLY.serialize(getAdv());
}
public String getPlainText() {
return PlainTextComponentSerializer.plainText().serializeOr(getAdv(), "");
}
public Chat then(Component comp) {
if (comp instanceof TextComponent txtComp) {
if (!txtComp.hasStyling() && (txtComp.content().isEmpty())) {
// no need to add the provided component to the current component.
// but eventual child component must be added
if (!txtComp.children().isEmpty()) {
for (Component child : txtComp.children())
then(child);
}
return this;
}
}
builder.append(comp);
return this;
}
public Chat then(BaseComponent subComponent) {
return then(toAdventure(subComponent));
}
public Chat then(Chat comp) {
return then(comp.getAdv());
}
public Chat then(BaseComponent[] components) {
return then(toAdventure(components));
}
public Chat thenText(Object plainText) { return then(text(plainText)); }
public Chat thenInfo(Object plainText) { return then(infoText(plainText)); }
public Chat thenWarning(Object plainText) { return then(warningText(plainText)); }
public Chat thenSuccess(Object plainText) { return then(successText(plainText)); }
public Chat thenFailure(Object plainText) { return then(failureText(plainText)); }
public Chat thenData(Object plainText) { return then(dataText(plainText)); }
public Chat thenDecoration(Object plainText) { return then(decorationText(plainText)); }
public Chat thenPlayerName(String legacyText) { return then(playerNameText(legacyText)); }
public Chat thenPlayerName(Component comp) { return then(playerNameComponent(comp)); }
public Chat thenNewLine() { return then(Component.newline()); }
public Chat thenLegacyText(Object legacyText) { return then(legacyText(legacyText)); }
public Chat thenTranslation(String key, Object... with) { return then(translation(key, with)); }
public Chat thenKeyBind(String key) { return then(keybind(key)); }
public Chat thenScore(String name, String objective) { return then(score(name, objective)); }
public Chat thenURLLink(Chat inner, String url, Chat hover) { return then(ChatUtil.createURLLink(inner, url, hover)); }
public Chat thenURLLink(Chat inner, String url) { return thenURLLink(inner, url, null); }
public Chat thenURLLink(String url, Chat hover) { return thenURLLink(text(url), url, hover); }
public Chat thenURLLink(String url) { return thenURLLink(text(url), url); }
public Chat thenCommandLink(Chat inner, String cmdWithSlash, Chat hover) { return then(ChatUtil.createCommandLink(inner, cmdWithSlash, hover)); }
public Chat thenCommandLink(Chat inner, String cmdWithSlash) { return thenCommandLink(inner, cmdWithSlash, null); }
public Chat thenCommandLink(String cmdWithSlash, Chat hover) { return thenCommandLink(text(cmdWithSlash), cmdWithSlash, hover); }
public Chat thenCommandLink(String cmdWithSlash) { return thenCommandLink(text(cmdWithSlash), cmdWithSlash); }
public Chat thenCommandSuggest(Chat inner, String cmdWithSlash, Chat hover) { return then(ChatUtil.createCommandSuggest(inner, cmdWithSlash, hover)); }
public Chat thenCommandSuggest(Chat inner, String cmdWithSlash) { return thenCommandSuggest(inner, cmdWithSlash, null); }
public Chat thenCommandSuggest(String cmdWithSlash, Chat hover) { return thenCommandSuggest(text(cmdWithSlash), cmdWithSlash, hover); }
public Chat thenCommandSuggest(String cmdWithSlash) { return thenCommandSuggest(text(cmdWithSlash), cmdWithSlash); }
/**
* Draws a full line with the default decoration char, colored with the default decoration color.
* @return this, for method chaining
*/
public Chat thenEmptyCharLine() {
return then(ChatUtil.emptyLine(config.decorationChar, config.decorationColor, console));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided Chat left aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenLeftTextCharLine(Chat leftText) {
return then(ChatUtil.leftText(chat().decorationColor().thenText(" ").then(leftText).thenText(" "), config.decorationChar,
config.decorationColor, config.nbCharMargin, console));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided component left aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenLeftTextCharLine(BaseComponent leftText) {
return thenLeftTextCharLine(chatComponent(leftText));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided Chat right aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenRightTextCharLine(Chat rightText) {
return then(ChatUtil.rightText(chat().decorationColor().thenText(" ").then(rightText).thenText(" "), config.decorationChar,
config.decorationColor, config.nbCharMargin, console));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided component right aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenRightTextCharLine(BaseComponent leftText) {
return thenRightTextCharLine(chatComponent(leftText));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided Chat centered on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenCenterTextCharLine(Chat centerText) {
return then(ChatUtil.centerText(chat().decorationColor().thenText(" ").then(centerText).thenText(" "), config.decorationChar,
config.decorationColor, console));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided component centered on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenCenterTextCharLine(BaseComponent leftText) {
return thenCenterTextCharLine(chatComponent(leftText));
}
public static final class FormatableChat extends Chat {
/* package */ FormatableChat(ComponentBuilder<?, ?> c) {
super(c);
}
public FormatableChat console(boolean c) { console = c; return this; }
public FormatableChat color(TextColor c) { builder.color(c); return this; }
public FormatableChat color(ChatColor c) { return color(c == null ? null : TextColor.color(c.getColor().getRGB())); }
public FormatableChat color(Color c) { return color(c == null ? null : TextColor.color(c.getRGB())); }
public FormatableChat color(String c) { return color(c == null ? null : ChatColor.of(c)); }
public FormatableChat black() { return color(NamedTextColor.BLACK); }
public FormatableChat darkBlue() { return color(NamedTextColor.DARK_BLUE); }
public FormatableChat darkGreen() { return color(NamedTextColor.DARK_GREEN); }
public FormatableChat darkAqua() { return color(NamedTextColor.DARK_AQUA); }
public FormatableChat darkRed() { return color(NamedTextColor.DARK_RED); }
public FormatableChat darkPurple() { return color(NamedTextColor.DARK_PURPLE); }
public FormatableChat gold() { return color(NamedTextColor.GOLD); }
public FormatableChat gray() { return color(NamedTextColor.GRAY); }
public FormatableChat darkGray() { return color(NamedTextColor.DARK_GRAY); }
public FormatableChat blue() { return color(NamedTextColor.BLUE); }
public FormatableChat green() { return color(NamedTextColor.GREEN); }
public FormatableChat aqua() { return color(NamedTextColor.AQUA); }
public FormatableChat red() { return color(NamedTextColor.RED); }
public FormatableChat lightPurple() { return color(NamedTextColor.LIGHT_PURPLE); }
public FormatableChat yellow() { return color(NamedTextColor.YELLOW); }
public FormatableChat white() { return color(NamedTextColor.WHITE); }
public FormatableChat successColor() { return color(config.successColor); }
public FormatableChat failureColor() { return color(config.failureColor); }
public FormatableChat infoColor() { return color(config.infoColor); }
public FormatableChat warningColor() { return color(config.warningColor); }
public FormatableChat dataColor() { return color(config.dataColor); }
public FormatableChat decorationColor() { return color(config.decorationColor); }
public FormatableChat urlColor() { return color(config.urlColor); }
public FormatableChat commandColor() { return color(config.commandColor); }
public FormatableChat highlightedCommandColor() { return color(config.highlightedCommandColor); }
public FormatableChat broadcastColor() { return color(config.broadcastColor); }
private FormatableChat setStyle(Consumer<Style.Builder> styleOp) { builder.style(styleOp); return this; }
private FormatableChat setDecoration(TextDecoration deco, Boolean state) {
return setStyle(b -> b.decoration(deco, State.byBoolean(state)));
}
public FormatableChat bold(Boolean b) { return setDecoration(TextDecoration.BOLD, b); }
public FormatableChat bold() { return bold(true); }
public FormatableChat italic(Boolean i) { return setDecoration(TextDecoration.ITALIC, i); }
public FormatableChat italic() { return italic(true); }
public FormatableChat underlined(Boolean u) { return setDecoration(TextDecoration.UNDERLINED, u); }
public FormatableChat underlined() { return underlined(true); }
public FormatableChat strikethrough(Boolean s) { return setDecoration(TextDecoration.STRIKETHROUGH, s); }
public FormatableChat strikethrough() { return strikethrough(true); }
public FormatableChat obfuscated(Boolean o) { return setDecoration(TextDecoration.OBFUSCATED, o); }
public FormatableChat obfuscated() { return obfuscated(true); }
public FormatableChat font(Key f) { return setStyle(s -> s.font(f)); }
public FormatableChat shiftClickInsertion(String i) { builder.insertion(i); return this; }
private FormatableChat click(ClickEvent e) { builder.clickEvent(e); return this; }
public FormatableChat clickCommand(String cmdWithSlash) { return click(ClickEvent.runCommand(cmdWithSlash)); }
public FormatableChat clickSuggest(String cmdWithSlash) { return click(ClickEvent.suggestCommand(cmdWithSlash)); }
public FormatableChat clickClipboard(String value) { return click(ClickEvent.copyToClipboard(value)); }
public FormatableChat clickURL(String url) { return click(ClickEvent.openUrl(url)); }
public FormatableChat clickBookPage(int page) { return click(ClickEvent.changePage(page)); }
public FormatableChat hover(HoverEventSource<?> e) { builder.hoverEvent(e); return this; }
public FormatableChat hover(Chat v) { return hover(v.getAdv()); }
public FormatableChat hover(BaseComponent v) { return hover(toAdventure(v)); }
public FormatableChat hover(BaseComponent[] v) { return hover(toAdventure(v)); }
public FormatableChat hover(String legacyText) { return hover(legacyText(legacyText)); }
}
@Override
public @NonNull HoverEvent<Component> asHoverEvent(@NonNull UnaryOperator<Component> op) {
return HoverEvent.showText(op.apply(getAdv()));
}
@Override
public @NonNull Component asComponent() {
return getAdv();
}
@Override
public boolean equals(Object obj) {
return obj instanceof Chat c
&& getAdv().equals(c.getAdv());
}
@Override
public int hashCode() {
return getAdv().hashCode();
}
@Override
public String toString() {
return getPlainText();
}
/* package */ static ComponentLike[] filterObjToComponentLike(Object[] values) {
if (values == null)
return null;
ComponentLike[] ret = new ComponentLike[values.length];
for (int i = 0; i < values.length; i++) {
Object v = values[i];
if (v instanceof BaseComponent[])
ret[i] = toAdventure((BaseComponent[]) v);
else if (v instanceof BaseComponent)
ret[i] = toAdventure((BaseComponent) v);
else if (v instanceof ComponentLike)
ret[i] = (ComponentLike) v;
else
ret[i] = Component.text(Objects.toString(v));
}
return ret;
}
public static Component toAdventure(BaseComponent[] components) {
return BungeeComponentSerializer.get().deserialize(components);
}
public static Component toAdventure(BaseComponent component) {
return toAdventure(new BaseComponent[] { component });
}
public static BaseComponent[] toBungeeArray(Component component) {
return BungeeComponentSerializer.get().serialize(component);
}
public static BaseComponent toBungee(Component component) {
BaseComponent[] arr = toBungeeArray(component);
return arr.length == 1 ? arr[0] : new net.md_5.bungee.api.chat.TextComponent(arr);
}
public static ComponentBuilder<?, ?> componentToBuilder(Component c) {
ComponentBuilder<?, ?> builder;
if (c instanceof TextComponent) {
builder = Component.text()
.content(((TextComponent) c).content());
}
else if (c instanceof TranslatableComponent) {
builder = Component.translatable()
.key(((TranslatableComponent) c).key())
.args(((TranslatableComponent) c).args());
}
else if (c instanceof SelectorComponent) {
builder = Component.selector()
.pattern(((SelectorComponent) c).pattern());
}
else if (c instanceof ScoreComponent) {
builder = Component.score()
.name(((ScoreComponent) c).name())
.objective(((ScoreComponent) c).objective());
}
else if (c instanceof KeybindComponent) {
builder = Component.keybind()
.keybind(((KeybindComponent) c).keybind());
}
else if (c instanceof BlockNBTComponent) {
builder = Component.blockNBT()
.interpret(((BlockNBTComponent) c).interpret())
.nbtPath(((BlockNBTComponent) c).nbtPath())
.pos(((BlockNBTComponent) c).pos());
}
else if (c instanceof EntityNBTComponent) {
builder = Component.entityNBT()
.interpret(((EntityNBTComponent) c).interpret())
.nbtPath(((EntityNBTComponent) c).nbtPath())
.selector(((EntityNBTComponent) c).selector());
}
else if (c instanceof StorageNBTComponent) {
builder = Component.storageNBT()
.interpret(((StorageNBTComponent) c).interpret())
.nbtPath(((StorageNBTComponent) c).nbtPath())
.storage(((StorageNBTComponent) c).storage());
}
else {
throw new IllegalArgumentException("Unknows component type " + c.getClass());
}
return builder.style(c.style()).append(c.children());
}
public static Chat italicFalseIfNotSet(Chat c) {
c.builder.style(b -> {
if (b.build().decoration(TextDecoration.ITALIC) == State.NOT_SET) {
((FormatableChat) c).italic(false);
}
});
return c;
}
protected static final Config config = new Config();
public static Config getConfig() {
return config;
}
public static class Config {
public TextColor decorationColor = NamedTextColor.YELLOW;
public char decorationChar = '-';
public int nbCharMargin = 1;
public TextColor successColor = NamedTextColor.GREEN;
public TextColor failureColor = NamedTextColor.RED;
public TextColor infoColor = NamedTextColor.GOLD;
public TextColor warningColor = NamedTextColor.GOLD;
public TextColor dataColor = NamedTextColor.GRAY;
public TextColor urlColor = NamedTextColor.GREEN;
public TextColor commandColor = NamedTextColor.GRAY;
public TextColor highlightedCommandColor = NamedTextColor.WHITE;
public TextColor broadcastColor = NamedTextColor.YELLOW;
public Supplier<Chat> prefix;
public int getPrefixWidth(boolean console) {
Chat c = prefix == null ? null : prefix.get();
return c == null ? 0 : ChatUtil.componentWidth(c.getAdv(), console);
}
}
}

View File

@@ -1,303 +0,0 @@
package fr.pandacube.lib.core.chat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.md_5.bungee.api.ChatColor;
public class ChatColorUtil {
public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoPpRr";
public static final String ALL_COLORS = "0123456789AaBbCcDdEeFf";
private static final Pattern HEX_COLOR_PATTERN = Pattern.compile("§x(?>§[\\da-f]){6}", Pattern.CASE_INSENSITIVE);
private static final Pattern ESS_COLOR_PATTERN = Pattern.compile("§#[\\da-f]{6}", Pattern.CASE_INSENSITIVE);
/**
* Return the legacy format needed to reproduce the format at the end of the provided legacy text.
* Supports standard chat colors and formats, BungeeCord Chat rgb format and EssentialsX rgb format.
* The RGB value from EssentialsX format is converted to BungeeCord Chat when included in the returned value.
*/
public static String getLastColors(String legacyText) {
StringBuilder result = new StringBuilder();
int length = legacyText.length();
for (int index = length - 2; index >= 0; index--) {
if (legacyText.charAt(index) == ChatColor.COLOR_CHAR) {
// detection of rgb color §x§0§1§2§3§4§5
String rgb;
if (index > 11
&& legacyText.charAt(index - 12) == ChatColor.COLOR_CHAR
&& (legacyText.charAt(index - 11) == 'x'
|| legacyText.charAt(index - 11) == 'X')
&& HEX_COLOR_PATTERN.matcher(rgb = legacyText.substring(index - 12, index + 2)).matches()) {
result.insert(0, rgb);
break;
}
// detection of rgb color §#012345 (and converting it to bungee chat format)
if (index < length - 7
&& legacyText.charAt(index + 1) == '#'
&& ESS_COLOR_PATTERN.matcher(rgb = legacyText.substring(index, index + 8)).matches()) {
rgb = "§x§" + rgb.charAt(2) + "§" + rgb.charAt(3)
+ "§" + rgb.charAt(4) + "§" + rgb.charAt(5)
+ "§" + rgb.charAt(6) + "§" + rgb.charAt(7);
result.insert(0, rgb);
break;
}
// try detect non-rgb format
char colorChar = legacyText.charAt(index + 1);
ChatColor legacyColor = getChatColorByChar(colorChar);
if (legacyColor != null) {
result.insert(0, legacyColor);
// Once we find a color or reset we can stop searching
char col = legacyColor.toString().charAt(1);
if ((col >= '0' && col <= '9')
|| (col >= 'a' && col <= 'f')
|| col == 'r') {
break;
}
}
}
}
return result.toString();
}
public static ChatColor getChatColorByChar(char code) {
return ChatColor.getByChar(Character.toLowerCase(code));
}
/**
* Translate the color code of the provided string, that uses the the color char, to
* the {@code §} color code format.
* <p>
* This method is the improved version of {@link ChatColor#translateAlternateColorCodes(char, String)},
* because it takes into account essentials RGB color code, and {@code altColorChar} escaping (by doubling it).
* Essentials RGB color code are converted to Bungee chat RGB format, so the returned string can be converted
* to component (see {@link Chat#legacyText(Object)}).
* <p>
* This method should be used for user input (no permission check) or string configuration, but not string
* from another API or containing URLs.
*/
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
{
char colorChar = ChatColor.COLOR_CHAR;
StringBuilder acc = new StringBuilder();
char[] b = textToTranslate.toCharArray();
for ( int i = 0; i < b.length; i++ )
{
if (i < b.length - 1 // legacy chat format
&& b[i] == altColorChar && ALL_CODES.indexOf(b[i + 1]) > -1)
{
acc.append(colorChar);
acc.append(lowerCase(b[i + 1]));
i++;
}
else if (i < b.length - 13 // bungee chat RGB format
&& b[i] == altColorChar
&& lowerCase(b[i + 1]) == 'x'
&& b[i + 2] == altColorChar && ALL_COLORS.indexOf(b[i + 3]) > -1
&& b[i + 4] == altColorChar && ALL_COLORS.indexOf(b[i + 5]) > -1
&& b[i + 6] == altColorChar && ALL_COLORS.indexOf(b[i + 7]) > -1
&& b[i + 8] == altColorChar && ALL_COLORS.indexOf(b[i + 9]) > -1
&& b[i + 10] == altColorChar && ALL_COLORS.indexOf(b[i + 11]) > -1
&& b[i + 12] == altColorChar && ALL_COLORS.indexOf(b[i + 13]) > -1) {
acc.append(colorChar).append(lowerCase(b[i + 1]));
acc.append(colorChar).append(lowerCase(b[i + 3]));
acc.append(colorChar).append(lowerCase(b[i + 5]));
acc.append(colorChar).append(lowerCase(b[i + 7]));
acc.append(colorChar).append(lowerCase(b[i + 9]));
acc.append(colorChar).append(lowerCase(b[i + 11]));
acc.append(colorChar).append(lowerCase(b[i + 13]));
i+=13;
}
else if (i < b.length - 7 // Essentials chat RGB format
&& b[i] == altColorChar
&& b[i + 1] == '#'
&& ALL_COLORS.indexOf(b[i + 2]) > -1 && ALL_COLORS.indexOf(b[i + 3]) > -1
&& ALL_COLORS.indexOf(b[i + 4]) > -1 && ALL_COLORS.indexOf(b[i + 5]) > -1
&& ALL_COLORS.indexOf(b[i + 6]) > -1 && ALL_COLORS.indexOf(b[i + 7]) > -1) {
acc.append(colorChar).append('x');
acc.append(colorChar).append(lowerCase(b[i + 2]));
acc.append(colorChar).append(lowerCase(b[i + 3]));
acc.append(colorChar).append(lowerCase(b[i + 4]));
acc.append(colorChar).append(lowerCase(b[i + 5]));
acc.append(colorChar).append(lowerCase(b[i + 6]));
acc.append(colorChar).append(lowerCase(b[i + 7]));
i+=7;
}
else if (i < b.length - 1 && b[i] == altColorChar && b[i + 1] == altColorChar) {
acc.append(altColorChar);
i++;
}
else {
acc.append(b[i]);
}
}
return acc.toString();
}
private static char lowerCase(char c) { return Character.toLowerCase(c); }
/**
* Force a text to be italic, while keeping other formatting and colors.
* The text is prefixed with the ITALIC tag, but is not reset at the end.
* @param legacyText the original text
* @return the text fully italic
*/
public static String forceItalic(String legacyText) {
return forceFormat(legacyText, ChatColor.ITALIC);
}
/**
* Force a text to be bold, while keeping other formatting and colors.
* The text is prefixed with the BOLD tag, but is not reset at the end.
* @param legacyText the original text
* @return the text fully bold
*/
public static String forceBold(String legacyText) {
return forceFormat(legacyText, ChatColor.BOLD);
}
/**
* Force a text to be underlined, while keeping other formatting and colors.
* The text is prefixed with the UNDERLINE tag, but is not reset at the end.
* @param legacyText the original text
* @return the text fully underlined
*/
public static String forceUnderline(String legacyText) {
return forceFormat(legacyText, ChatColor.UNDERLINE);
}
/**
* Force a text to be stroked through, while keeping other formatting and colors.
* The text is prefixed with the STRIKETHROUGH tag, but is not reset at the end.
* @param legacyText the original text
* @return the text fully stroked through
*/
public static String forceStrikethrough(String legacyText) {
return forceFormat(legacyText, ChatColor.STRIKETHROUGH);
}
/**
* Force a text to be obfuscated, while keeping other formatting and colors.
* The text is prefixed with the MAGIC tag, but is not reset at the end.
* @param legacyText the original text
* @return the text fully obfuscated
*/
public static String forceObfuscated(String legacyText) {
return forceFormat(legacyText, ChatColor.MAGIC);
}
private static String forceFormat(String legacyText, ChatColor format) {
return format + legacyText
.replace(format.toString(), "") // remove previous tag to make the result cleaner
.replaceAll("§([a-frA-FR\\d])", "§$1" + format);
}
/**
* Replace the RESET tag of the input string to the specified color tag.
* @param legacyText the original text
* @param color the color to used to replace the RESET tag
* (can be a combination of a color tag followed by multiple format tag)
* @return the resulting text
*/
public static String resetToColor(String legacyText, String color) {
return legacyText.replace(ChatColor.RESET.toString(), color);
}
public static TextColor toAdventure(ChatColor bungee) {
if (bungee == null)
return null;
if (bungee.getColor() == null)
throw new IllegalArgumentException("The provided Bungee ChatColor must be an actual color (not format nor reset).");
return TextColor.color(bungee.getColor().getRGB());
}
public static ChatColor toBungee(TextColor col) {
if (col == null)
return null;
if (col instanceof NamedTextColor) {
return ChatColor.of(((NamedTextColor) col).toString());
}
return ChatColor.of(col.asHexString());
}
public static TextColor interpolateColor(float v0, float v1, float v, TextColor cc0, TextColor cc1) {
float normV = (v - v0) / (v1 - v0);
return TextColor.lerp(normV, cc0, cc1);
}
public static class ChatValueGradient {
private record GradientValueColor(float value, TextColor color) { } // Java 16
final List<GradientValueColor> colors = new ArrayList<>();
public synchronized ChatValueGradient add(float v, TextColor col) {
colors.add(new GradientValueColor(v, col));
return this;
}
public synchronized TextColor pickColorAt(float v) {
if (colors.isEmpty())
throw new IllegalStateException("Must define at least one color in this ChatValueGradient instance.");
if (colors.size() == 1)
return colors.get(0).color();
colors.sort((p1, p2) -> Float.compare(p1.value(), p2.value()));
if (v <= colors.get(0).value())
return colors.get(0).color();
if (v >= colors.get(colors.size() - 1).value())
return colors.get(colors.size() - 1).color();
int p1 = 1;
for (; p1 < colors.size(); p1++) {
if (colors.get(p1).value() >= v)
break;
}
int p0 = p1 - 1;
float v0 = colors.get(p0).value(), v1 = colors.get(p1).value();
TextColor cc0 = colors.get(p0).color(), cc1 = colors.get(p1).color();
return interpolateColor(v0, v1, v, cc0, cc1);
}
}
}

View File

@@ -1,118 +0,0 @@
package fr.pandacube.lib.core.chat;
import java.util.Objects;
import fr.pandacube.lib.core.chat.Chat.FormatableChat;
import fr.pandacube.lib.core.util.Log;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.md_5.bungee.api.chat.BaseComponent;
public abstract class ChatStatic {
public static FormatableChat chatComponent(Component c) {
return new FormatableChat(Chat.componentToBuilder(c));
}
public static FormatableChat chatComponent(BaseComponent c) {
return new FormatableChat(Chat.componentToBuilder(Chat.toAdventure(c)));
}
public static FormatableChat chatComponent(Chat c) {
return chatComponent(c.getAdv());
}
public static FormatableChat chat() {
return new FormatableChat(Component.text());
}
public static FormatableChat chatComponent(BaseComponent[] c) {
return chatComponent(Chat.toAdventure(c));
}
public static FormatableChat text(Object plainText) {
if (plainText instanceof Chat) {
Log.warning("Using Chat instance as plain text. Please use proper API method. Ill properly use your Chat instance this time...", new Throwable());
return (FormatableChat) plainText;
}
if (plainText instanceof Component) {
Log.warning("Using Component instance as plain text. Please use proper API method. Ill properly use your Component this time...", new Throwable());
return chatComponent((Component) plainText);
}
return new FormatableChat(Component.text().content(Objects.toString(plainText)));
}
public static FormatableChat legacyText(Object legacyText) {
if (legacyText instanceof Chat) {
Log.warning("Using Chat instance as legacy text. Please use proper API method. Ill properly use your Chat instance this time...", new Throwable());
return (FormatableChat) legacyText;
}
if (legacyText instanceof Component) {
Log.warning("Using Component instance as legacy text. Please use proper API method. Ill properly use your Component this time...", new Throwable());
return chatComponent((Component) legacyText);
}
return chatComponent(LegacyComponentSerializer.legacySection().deserialize(Objects.toString(legacyText)));
}
public static FormatableChat infoText(Object plainText) {
return text(plainText).infoColor();
}
public static FormatableChat warningText(Object plainText) {
return text(plainText).warningColor();
}
public static FormatableChat dataText(Object plainText) {
return text(plainText).dataColor();
}
public static FormatableChat decorationText(Object plainText) {
return text(plainText).decorationColor();
}
public static FormatableChat successText(Object plainText) {
return text(plainText).successColor();
}
public static FormatableChat failureText(Object plainText) {
return text(plainText).failureColor();
}
public static FormatableChat playerNameText(String legacyText) {
FormatableChat fc = legacyText(legacyText);
fc.builder.colorIfAbsent(NamedTextColor.WHITE);
return fc;
}
public static FormatableChat playerNameComponent(Component c) {
FormatableChat fc = chatComponent(c);
fc.builder.colorIfAbsent(NamedTextColor.WHITE);
return fc;
}
public static FormatableChat translation(String key, Object... with) {
return new FormatableChat(Component.translatable()
.key(key)
.args(Chat.filterObjToComponentLike(with))
);
}
public static FormatableChat keybind(String key) {
return new FormatableChat(Component.keybind()
.keybind(key)
);
}
public static FormatableChat score(String name, String objective) {
return new FormatableChat(Component.score()
.name(name)
.objective(objective)
);
}
}

View File

@@ -1,714 +0,0 @@
package fr.pandacube.lib.core.chat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableMap;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.format.TextDecoration.State;
import net.md_5.bungee.api.ChatColor;
import fr.pandacube.lib.core.chat.Chat.FormatableChat;
import static fr.pandacube.lib.core.chat.ChatStatic.chat;
import static fr.pandacube.lib.core.chat.ChatStatic.legacyText;
import static fr.pandacube.lib.core.chat.ChatStatic.text;
public class ChatUtil {
public static final int DEFAULT_CHAR_SIZE = 6;
public static final Map<Integer, String> CHARS_SIZE = new ImmutableMap.Builder<Integer, String>()
.put(-6, "§")
.put(2, "!.,:;i|¡'")
.put(3, "`lìí")
.put(4, " I[]tï×")
.put(5, "\"()*<>fk{}")
.put(7, "@~®©«»")
.put(9, "├└")
.build();
public static final int DEFAULT_CHAT_WIDTH = 320;
public static final int SIGN_WIDTH = 90;
public static final int BOOK_WIDTH = 116;
public static final int MOTD_WIDTH = 270;
public static final int BEDROCK_FORM_WIDE_BUTTON = 178;
public static final int CONSOLE_NB_CHAR_DEFAULT = 50;
public static FormatableChat createURLLink(String text, String url) {
return createURLLink(legacyText(text), url, null);
}
public static FormatableChat createURLLink(String text, String url, String hoverText) {
return createURLLink(legacyText(text), url, hoverText != null ? legacyText(hoverText) : null);
}
/* package */ static FormatableChat createURLLink(Chat element, String url, Chat hover) {
String dispURL = (url.length() > 50) ? (url.substring(0, 48) + "...") : url;
return (FormatableChat) chat()
.clickURL(url)
.urlColor()
.hover(
hover != null ? hover : Chat.text(dispURL)
)
.then(element);
}
public static FormatableChat createCommandLink(String text, String commandWithSlash, String hoverText) {
return createCommandLink(text, commandWithSlash, hoverText == null ? null : legacyText(hoverText));
}
public static FormatableChat createCommandLink(String text, String commandWithSlash, Chat hoverText) {
return createCommandLink(legacyText(text), commandWithSlash, hoverText);
}
/* package */ static FormatableChat createCommandLink(Chat d, String commandWithSlash, Chat hoverText) {
FormatableChat c = chat()
.clickCommand(commandWithSlash)
.commandColor();
if (hoverText != null)
c.hover(hoverText);
return (FormatableChat) c.then(d);
}
public static FormatableChat createCommandSuggest(String text, String commandWithSlash, String hoverText) {
return createCommandSuggest(text, commandWithSlash, hoverText == null ? null : legacyText(hoverText));
}
public static FormatableChat createCommandSuggest(String text, String commandWithSlash, Chat hoverText) {
return createCommandSuggest(legacyText(text), commandWithSlash, hoverText);
}
/* package */ static FormatableChat createCommandSuggest(Chat d, String commandWithSlash, Chat hoverText) {
FormatableChat c = chat()
.clickSuggest(commandWithSlash)
.commandColor();
if (hoverText != null)
c.hover(hoverText);
return (FormatableChat) c.then(d);
}
/**
* @param cmdFormat the command with %d inside to be replaced with the page number (must start with slash)
*/
public static Chat createPagination(String prefix, String cmdFormat, int currentPage, int nbPages, int nbPagesToDisplay) {
Set<Integer> pagesToDisplay = new TreeSet<>();
for (int i = 0; i < nbPagesToDisplay && i < nbPages && nbPages - i > 0; i++) {
pagesToDisplay.add(i + 1);
pagesToDisplay.add(nbPages - i);
}
for (int i = currentPage - nbPagesToDisplay + 1; i < currentPage + nbPagesToDisplay; i++) {
if (i > 0 && i <= nbPages)
pagesToDisplay.add(i);
}
Chat d = chat().thenLegacyText(prefix);
boolean first = true;
int previous = 0;
for (int page : pagesToDisplay) {
if (!first) {
if (page == previous + 1) {
d.thenText(" ");
}
else {
if (cmdFormat.endsWith("%d")) {
d.thenText(" ");
d.then(createCommandSuggest("...", cmdFormat.substring(0, cmdFormat.length() - 2), "Choisir la page"));
d.thenText(" ");
}
else
d.thenText(" ... ");
}
}
else
first = false;
FormatableChat pDisp = createCommandLink(Integer.toString(page), String.format(cmdFormat, page), "Aller à la page " + page);
if (page == currentPage) {
pDisp.highlightedCommandColor();
}
d.then(pDisp);
previous = page;
}
return d;
}
/**
* @param decorationColor support null values
*/
public static Chat centerText(Chat text, char repeatedChar, TextColor decorationColor, boolean console) {
return centerText(text, repeatedChar, decorationColor, console, console ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
}
public static Chat centerText(Chat text, char repeatedChar, TextColor decorationColor, boolean console, int maxWidth) {
return centerText(text, repeatedChar, decorationColor, false, console, maxWidth);
}
/**
* @param decorationColor support null values
*/
public static Chat centerText(Chat text, char repeatedChar, TextColor decorationColor, boolean decorationBold, boolean console, int maxWidth) {
int textWidth = componentWidth(text.getAdv(), console);
if (textWidth > maxWidth)
return text;
int repeatedCharWidth = charW(repeatedChar, console, decorationBold);
int sideWidth = (maxWidth - textWidth) / 2;
int sideNbChar = sideWidth / repeatedCharWidth;
if (sideNbChar == 0)
return text;
String sideChars = repeatedChar(repeatedChar, sideNbChar);
FormatableChat side = text(sideChars).color(decorationColor);
if (decorationBold)
side.bold();
Chat d = Chat.chat()
.then(side)
.then(text);
if (repeatedChar != ' ')
d.then(side);
return d;
}
public static Chat leftText(Chat text, char repeatedChar, TextColor decorationColor, int nbLeft, boolean console) {
return leftText(text, repeatedChar, decorationColor, nbLeft, console, console ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
}
public static Chat leftText(Chat text, char repeatedChar, TextColor decorationColor, int nbLeft, boolean console, int maxWidth) {
int textWidth = componentWidth(text.getAdv(), console);
int repeatedCharWidth = charW(repeatedChar, console, false);
int leftWidth = nbLeft * repeatedCharWidth;
if (textWidth + leftWidth > maxWidth)
return text;
int rightNbChar = (maxWidth - (textWidth + leftWidth)) / repeatedCharWidth;
Chat d = chat()
.then(text(repeatedChar(repeatedChar, nbLeft)).color(decorationColor))
.then(text);
if (repeatedChar != ' ') {
d.then(text(repeatedChar(repeatedChar, rightNbChar)).color(decorationColor));
}
return d;
}
public static Chat rightText(Chat text, char repeatedChar, TextColor decorationColor, int nbRight, boolean console) {
return rightText(text, repeatedChar, decorationColor, nbRight, console, console ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
}
public static Chat rightText(Chat text, char repeatedChar, TextColor decorationColor, int nbRight,
boolean console, int maxWidth) {
int textWidth = componentWidth(text.getAdv(), console);
int repeatedCharWidth = charW(repeatedChar, console, false);
int rightWidth = nbRight * repeatedCharWidth;
if (textWidth + rightWidth > maxWidth)
return text;
int leftNbChar = (maxWidth - (textWidth + rightWidth)) / repeatedCharWidth;
Chat d = chat()
.then(text(repeatedChar(repeatedChar, leftNbChar)).color(decorationColor))
.then(text);
if (repeatedChar != ' ') {
d.then(text(repeatedChar(repeatedChar, nbRight)).color(decorationColor));
}
return d;
}
public static Chat emptyLine(char repeatedChar, TextColor decorationColor, boolean console) {
return emptyLine(repeatedChar, decorationColor, false, console);
}
public static Chat emptyLine(char repeatedChar, TextColor decorationColor, boolean decorationBold, boolean console) {
return emptyLine(repeatedChar, decorationColor, decorationBold, console, (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
}
public static Chat emptyLine(char repeatedChar, TextColor decorationColor, boolean decorationBold, boolean console, int maxWidth) {
int count = maxWidth / charW(repeatedChar, console, decorationBold);
FormatableChat line = text(repeatedChar(repeatedChar, count)).color(decorationColor);
if (decorationBold)
line.bold();
return line;
}
private static String repeatedChar(char repeatedChar, int count) {
char[] c = new char[count];
Arrays.fill(c, repeatedChar);
return new String(c);
}
public static int componentWidth(Component component, boolean console) {
return componentWidth(component, console, false);
}
public static int componentWidth(Component component, boolean console, boolean parentBold) {
if (component == null)
return 0;
int count = 0;
State currentBold = component.style().decoration(TextDecoration.BOLD);
boolean actuallyBold = childBold(parentBold, currentBold);
if (component instanceof TextComponent) {
count += strWidth(((TextComponent)component).content(), console, actuallyBold);
}
else if (component instanceof TranslatableComponent) {
for (Component c : ((TranslatableComponent)component).args())
count += componentWidth(c, console, actuallyBold);
}
for (Component c : component.children())
count += componentWidth(c, console, actuallyBold);
return count;
}
private static boolean childBold(boolean parent, TextDecoration.State child) {
return (parent && child != State.FALSE) || (!parent && child == State.TRUE);
}
public static int strWidth(String str, boolean console, boolean bold) {
int count = 0;
for (char c : str.toCharArray())
count += charW(c, console, bold);
return Math.max(count, 0);
}
public static int charW(char c, boolean console, boolean bold) {
if (console) return (c == '§') ? -1 : 1;
for (int px : CHARS_SIZE.keySet())
if (CHARS_SIZE.get(px).indexOf(c) >= 0) return px + (bold ? 1 : 0);
return 6 + (bold ? 1 : 0);
}
public static List<Chat> wrapInLimitedPixelsToChat(String legacyText, int pixelWidth) {
return wrapInLimitedPixels(legacyText, pixelWidth).stream()
.map(ChatStatic::legacyText)
.collect(Collectors.toList());
}
public static List<String> wrapInLimitedPixels(String legacyText, int pixelWidth) {
List<String> lines = new ArrayList<>();
legacyText += "\n"; // workaround to force algorithm to compute last lines;
String currentLine = "";
int currentLineSize = 0;
int index = 0;
StringBuilder currentWord = new StringBuilder();
int currentWordSize = 0;
boolean bold = false;
boolean firstCharCurrentWordBold = false;
do {
char c = legacyText.charAt(index);
if (c == ChatColor.COLOR_CHAR && index < legacyText.length() - 1) {
currentWord.append(c);
c = legacyText.charAt(++index);
currentWord.append(c);
if (c == 'l' || c == 'L') // bold
bold = true;
if ((c >= '0' && c <= '9') // reset bold
|| (c >= 'a' && c <= 'f')
|| (c >= 'A' && c <= 'F')
|| c == 'r' || c == 'R'
|| c == 'x' || c == 'X')
bold = false;
}
else if (c == ' ') {
if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word
lines.add(currentLine);
String lastStyle = ChatColorUtil.getLastColors(currentLine);
if (currentWord.charAt(0) == ' ') {
currentWord = new StringBuilder(currentWord.substring(1));
currentWordSize -= charW(' ', false, firstCharCurrentWordBold);
}
currentLine = (lastStyle.equals("§r") ? "" : lastStyle) + currentWord;
currentLineSize = currentWordSize;
}
else {
currentLine += currentWord;
currentLineSize += currentWordSize;
}
currentWord = new StringBuilder("" + c);
currentWordSize = charW(c, false, bold);
firstCharCurrentWordBold = bold;
}
else if (c == '\n') {
if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word
lines.add(currentLine);
String lastStyle = ChatColorUtil.getLastColors(currentLine);
if (currentWord.charAt(0) == ' ') {
currentWord = new StringBuilder(currentWord.substring(1));
}
currentLine = (lastStyle.equals("§r") ? "" : lastStyle) + currentWord;
}
else {
currentLine += currentWord;
}
// wrap after
lines.add(currentLine);
String lastStyle = ChatColorUtil.getLastColors(currentLine);
currentLine = lastStyle.equals("§r") ? "" : lastStyle;
currentLineSize = 0;
currentWord = new StringBuilder();
currentWordSize = 0;
firstCharCurrentWordBold = bold;
}
else {
currentWord.append(c);
currentWordSize += charW(c, false, bold);
}
} while(++index < legacyText.length());
return lines;
}
public static List<Component> renderTable(List<List<Chat>> rows, String space, boolean console) {
List<List<Component>> compRows = new ArrayList<>(rows.size());
for (List<Chat> row : rows) {
List<Component> compRow = new ArrayList<>(row.size());
for (Chat c : row) {
compRow.add(c.getAdv());
}
compRows.add(compRow);
}
return renderTableComp(compRows, space, console);
}
public static List<Component> renderTableComp(List<List<Component>> rows, String space, boolean console) {
// determine columns width
List<Integer> nbPixelPerColumn = new ArrayList<>();
for (List<Component> row : rows) {
for (int i = 0; i < row.size(); i++) {
int w = componentWidth(row.get(i), console);
if (nbPixelPerColumn.size() <= i)
nbPixelPerColumn.add(w);
else if (nbPixelPerColumn.get(i) < w)
nbPixelPerColumn.set(i, w);
}
}
// create the lines with appropriate spacing
List<Component> spacedRows = new ArrayList<>(rows.size());
for (List<Component> row : rows) {
Chat spacedRow = Chat.chat();
for (int i = 0; i < row.size() - 1; i++) {
int w = componentWidth(row.get(i), console);
int padding = nbPixelPerColumn.get(i) - w;
spacedRow.then(row.get(i));
spacedRow.then(customWidthSpace(padding, console));
spacedRow.thenText(space);
}
if (!row.isEmpty())
spacedRow.then(row.get(row.size() - 1));
spacedRows.add(spacedRow.getAdv());
}
return spacedRows;
}
public static Component customWidthSpace(int width, boolean console) {
if (console)
return Chat.text(" ".repeat(width)).getAdv();
return switch (width) {
case 0, 1 -> Component.empty();
case 2 -> Chat.text(".").black().getAdv();
case 3 -> Chat.text("`").black().getAdv();
case 6 -> Chat.text(". ").black().getAdv();
case 7 -> Chat.text("` ").black().getAdv();
case 11 -> Chat.text("` ").black().getAdv();
default -> {
int nbSpace = width / 4;
int nbBold = width % 4;
int nbNotBold = nbSpace - nbBold;
if (nbNotBold > 0) {
if (nbBold > 0) {
yield Chat.text(" ".repeat(nbNotBold)).bold(false)
.then(Chat.text(" ".repeat(nbBold)).bold(true))
.getAdv();
}
else
yield Chat.text(" ".repeat(nbNotBold)).bold(false).getAdv();
}
else if (nbBold > 0) {
yield Chat.text(" ".repeat(nbBold)).bold(true).getAdv();
}
throw new IllegalStateException("Should not be here (width=" + width + "; nbSpace=" + nbSpace + "; nbBold=" + nbBold + "; nbNotBold=" + nbNotBold + ")");
}
};
// "." is 2 px
// "`" is 3 px
// " " is 4 px
// 0 ""
// 1 ""
// 2 "."
// 3 "`"
// 4 " "
// 5 "§l "
// 6 ". "
// 7 "` "
// 8 " "
// 9 " §l "
// 10 "§l "
// 11 "` "
// 12 " "
}
private static final String PROGRESS_BAR_START = "[";
private static final String PROGRESS_BAR_END = "]";
private static final TextColor PROGRESS_BAR_EMPTY_COLOR = NamedTextColor.DARK_GRAY;
private static final char PROGRESS_BAR_EMPTY_CHAR = '.';
private static final char PROGRESS_BAR_FULL_CHAR = '|';
public static Chat progressBar(double[] values, TextColor[] colors, double total, int pixelWidth, boolean console) {
// 1. Compute char size for each values
int progressPixelWidth = pixelWidth - strWidth(PROGRESS_BAR_START + PROGRESS_BAR_END, console, false);
int charPixelWidth = charW(PROGRESS_BAR_EMPTY_CHAR, console, false);
assert charPixelWidth == charW(PROGRESS_BAR_FULL_CHAR, console, false) : "PROGRESS_BAR_EMPTY_CHAR and PROGRESS_BAR_FULL_CHAR should have the same pixel width according to #charW(...)";
int progressCharWidth = progressPixelWidth / charPixelWidth;
int[] sizes = new int[values.length];
double sumValuesBefore = 0;
int sumSizesBefore = 0;
for (int i = 0; i < values.length; i++) {
sumValuesBefore += values[i];
int charPosition = Math.min((int) Math.round(progressCharWidth * sumValuesBefore / total), progressCharWidth);
sizes[i] = charPosition - sumSizesBefore;
sumSizesBefore += sizes[i];
}
// 2. Generate rendered text
Chat c = text(PROGRESS_BAR_START);
int sumSizes = 0;
for (int i = 0; i < sizes.length; i++) {
sumSizes += sizes[i];
FormatableChat subC = text(repeatedChar(PROGRESS_BAR_FULL_CHAR, sizes[i]));
if (colors != null && i < colors.length && colors[i] != null)
subC.color(colors[i]);
c.then(subC);
}
return c
.then(text(repeatedChar(PROGRESS_BAR_EMPTY_CHAR, progressCharWidth - sumSizes))
.color(PROGRESS_BAR_EMPTY_COLOR))
.thenText(PROGRESS_BAR_END);
}
public static Chat progressBar(double value, TextColor color, double total, int pixelWidth, boolean console) {
return progressBar(new double[] { value }, new TextColor[] { color }, total, pixelWidth, console);
}
public static String truncatePrefix(String prefix, int maxLength) {
if (prefix.length() > maxLength) {
String lastColor = ChatColorUtil.getLastColors(prefix);
prefix = truncateAtLengthWithoutReset(prefix, maxLength);
if (!ChatColorUtil.getLastColors(prefix).equals(lastColor))
prefix = truncateAtLengthWithoutReset(prefix, maxLength - lastColor.length()) + lastColor;
}
return prefix;
}
public static String truncateAtLengthWithoutReset(String prefix, int l) {
if (prefix.length() > l) {
prefix = prefix.substring(0, l);
if (prefix.endsWith("§"))
prefix = prefix.substring(0, prefix.length()-1);
}
return prefix;
}
private static final String TREE_MIDDLE_CONNECTED = "";
private static final String TREE_END_CONNECTED = "";
private static final String TREE_MIDDLE_OPEN = "│§0`§r";
private static final String TREE_END_OPEN = "§0```§r";
private static final String TREE_MIDDLE_OPEN_CONSOLE = "";
private static final String TREE_END_OPEN_CONSOLE = " "; // nbsp
/**
* Generate a tree view based on the tree structure {@code node}.
*
* Each element in the returned list represent 1 line of the tree view.
* Thus, the caller may send each line separately or at once depending of the quantity of data.
* @return A array of component, each element being a single line.
*/
public static List<Chat> treeView(DisplayTreeNode node, boolean console) {
List<Chat> ret = new ArrayList<>();
ret.add(chat()
.then(node.component));
for (int i = 0; i < node.children.size(); i++) {
List<Chat> childComponents = treeView(node.children.get(i), console);
boolean last = i == node.children.size() - 1;
for (int j = 0; j < childComponents.size(); j++) {
String prefix = last ? (j == 0 ? TREE_END_CONNECTED : (console ? TREE_END_OPEN_CONSOLE : TREE_END_OPEN))
: (j == 0 ? TREE_MIDDLE_CONNECTED : (console ? TREE_MIDDLE_OPEN_CONSOLE : TREE_MIDDLE_OPEN));
ret.add(text(prefix)
.then(childComponents.get(j)));
}
}
return ret;
}
public static class DisplayTreeNode {
public final Chat component;
public final List<DisplayTreeNode> children = new ArrayList<>();
public DisplayTreeNode(Chat cmp) {
component = cmp;
}
public DisplayTreeNode addChild(DisplayTreeNode child) {
children.add(child);
return this;
}
}
}

View File

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

View File

@@ -11,7 +11,8 @@ import java.util.stream.Collectors;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import fr.pandacube.lib.core.util.ListUtil;
import fr.pandacube.lib.util.ListUtil;
import fr.pandacube.lib.util.TimeUtil;
@FunctionalInterface
public interface SuggestionsSupplier<S> {
@@ -183,6 +184,49 @@ public interface SuggestionsSupplier<S> {
return (s, ti, token, a) -> collectFilteredStream(LongStream.rangeClosed(min, max).mapToObj(Long::toString), token);
}
}
public static <S> SuggestionsSupplier<S> suggestDuration() {
final List<String> emptyTokenSuggestions = TimeUtil.DURATION_SUFFIXES.stream().map(p -> "1" + p).collect(Collectors.toList());
return (s, ti, token, args) -> {
if (token.isEmpty()) {
return emptyTokenSuggestions;
}
List<String> remainingSuffixes = new ArrayList<>(TimeUtil.DURATION_SUFFIXES);
char[] tokenChars = token.toCharArray();
String accSuffix = "";
for (char c : tokenChars) {
if (Character.isDigit(c)) {
scanAndRemovePastSuffixes(remainingSuffixes, accSuffix);
accSuffix = "";
} else if (Character.isLetter(c)) {
accSuffix += c;
} else
return Collections.emptyList();
}
String prefixToken = token.substring(0, token.length() - accSuffix.length());
return SuggestionsSupplier.collectFilteredStream(remainingSuffixes.stream(), accSuffix)
.stream()
.map(str -> prefixToken + str)
.collect(Collectors.toList());
};
}
private static void scanAndRemovePastSuffixes(List<String> suffixes, String foundSuffix) {
for (int i = 0; i < suffixes.size(); i++) {
if (foundSuffix.startsWith(suffixes.get(i))) {
suffixes.subList(0, i + 1).clear();
return;
}
}
}

View File

@@ -8,8 +8,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import fr.pandacube.lib.core.chat.ChatColorUtil;
import fr.pandacube.lib.core.util.Log;
import fr.pandacube.lib.chat.ChatColorUtil;
import fr.pandacube.lib.util.Log;
/**
* Class tht loads a specific config file or directory
*

View File

@@ -1,394 +0,0 @@
package fr.pandacube.lib.core.db;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import fr.pandacube.lib.core.util.Log;
/**
* Static class to handle most of the database operations.
*
* To use this database library, first call {@link #init(DBConnection, String)} with an appropriate {@link DBConnection},
* they you can initialize every table you need for your application, using {@link #initTable(Class)}.
*
* @author Marc Baloup
*/
public final class DB {
private static final List<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
private static final Map<Class<? extends SQLElement<?>>, String> tableNames = new HashMap<>();
private static DBConnection connection;
/* package */ static String tablePrefix = "";
public static DBConnection getConnection() {
return connection;
}
public synchronized static void init(DBConnection conn, String tablePrefix) {
connection = conn;
DB.tablePrefix = Objects.requireNonNull(tablePrefix);
}
public static synchronized <E extends SQLElement<E>> void initTable(Class<E> elemClass) throws DBInitTableException {
if (connection == null) {
throw new DBInitTableException(elemClass, "Database connection is not yet initialized.");
}
if (tables.contains(elemClass)) return;
try {
tables.add(elemClass);
Log.debug("[DB] Start Init SQL table "+elemClass.getSimpleName());
E instance = elemClass.getConstructor().newInstance();
String tableName = tablePrefix + instance.tableName();
tableNames.put(elemClass, tableName);
if (!tableExistInDB(tableName)) createTable(instance);
Log.debug("[DB] End init SQL table "+elemClass.getSimpleName());
} catch (Exception|ExceptionInInitializerError e) {
throw new DBInitTableException(elemClass, e);
}
}
private static <E extends SQLElement<E>> void createTable(E elem) throws SQLException {
String tableName = tablePrefix + elem.tableName();
StringBuilder sql = new StringBuilder("CREATE TABLE IF NOT EXISTS " + tableName + " (");
List<Object> params = new ArrayList<>();
Collection<SQLField<E, ?>> tableFields = elem.getFields().values();
boolean first = true;
for (SQLField<E, ?> f : tableFields) {
ParameterizedSQLString statementPart = f.forSQLPreparedStatement();
params.addAll(statementPart.parameters());
if (!first)
sql.append(", ");
first = false;
sql.append(statementPart.sqlString());
}
sql.append(", PRIMARY KEY id(id))");
try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql.toString())) {
int i = 1;
for (Object val : params)
ps.setObject(i++, val);
Log.info("Creating table " + elem.tableName() + ":\n" + ps.toString());
ps.executeUpdate();
}
}
public static <E extends SQLElement<E>> String getTableName(Class<E> elemClass) throws DBException {
initTable(elemClass);
return tableNames.get(elemClass);
}
private static boolean tableExistInDB(String tableName) throws SQLException {
try (ResultSet set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null)) {
return set.next();
}
}
@SuppressWarnings("unchecked")
public static <E extends SQLElement<E>> SQLField<E, Integer> getSQLIdField(Class<E> elemClass)
throws DBInitTableException {
initTable(elemClass);
return (SQLField<E, Integer>) SQLElement.fieldsCache.get(elemClass).get("id");
}
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Integer... ids) throws DBException {
return getByIds(elemClass, Arrays.asList(ids));
}
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Collection<Integer> ids)
throws DBException {
return getAll(elemClass, getSQLIdField(elemClass).in(ids), SQLOrderBy.asc(getSQLIdField(elemClass)), 1, null);
}
public static <E extends SQLElement<E>> E getById(Class<E> elemClass, int id) throws DBException {
return getFirst(elemClass, getSQLIdField(elemClass).eq(id));
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where)
throws DBException {
return getFirst(elemClass, where, null, null);
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLOrderBy<E> orderBy)
throws DBException {
return getFirst(elemClass, null, orderBy, null);
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy)
throws DBException {
return getFirst(elemClass, where, orderBy, null);
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy, Integer offset)
throws DBException {
SQLElementList<E> elts = getAll(elemClass, where, orderBy, 1, offset);
return (elts.size() == 0) ? null : elts.get(0);
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass) throws DBException {
return getAll(elemClass, null, null, null, null);
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where) throws DBException {
return getAll(elemClass, where, null, null, null);
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy) throws DBException {
return getAll(elemClass, where, orderBy, null, null);
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy, Integer limit) throws DBException {
return getAll(elemClass, where, orderBy, limit, null);
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy, Integer limit, Integer offset) throws DBException {
SQLElementList<E> elmts = new SQLElementList<>();
forEach(elemClass, where, orderBy, limit, offset, elmts::add);
return elmts;
}
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, Consumer<E> action) throws DBException {
forEach(elemClass, null, null, null, null, action);
}
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
Consumer<E> action) throws DBException {
forEach(elemClass, where, null, null, null, action);
}
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy, Consumer<E> action) throws DBException {
forEach(elemClass, where, orderBy, null, null, action);
}
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy, Integer limit, Consumer<E> action) throws DBException {
forEach(elemClass, where, orderBy, limit, null, action);
}
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy, Integer limit, Integer offset, Consumer<E> action) throws DBException {
initTable(elemClass);
try {
String sql = "SELECT * FROM " + getTableName(elemClass);
List<Object> params = new ArrayList<>();
if (where != null) {
ParameterizedSQLString ret = where.toSQL();
sql += " WHERE " + ret.sqlString();
params.addAll(ret.parameters());
}
if (orderBy != null) sql += " ORDER BY " + orderBy.toSQL();
if (limit != null) sql += " LIMIT " + limit;
if (offset != null) sql += " OFFSET " + offset;
sql += ";";
try (ResultSet set = customQueryStatement(sql, params)) {
while (set.next()) {
E elm = getElementInstance(set, elemClass);
action.accept(elm);
}
}
} catch (SQLException e) {
throw new DBException(e);
}
}
public static <E extends SQLElement<E>> long count(Class<E> elemClass) throws DBException {
return count(elemClass, null);
}
public static <E extends SQLElement<E>> long count(Class<E> elemClass, SQLWhere<E> where) throws DBException {
initTable(elemClass);
try {
String sql = "SELECT COUNT(*) as count FROM " + getTableName(elemClass);
List<Object> params = new ArrayList<>();
if (where != null) {
ParameterizedSQLString ret = where.toSQL();
sql += " WHERE " + ret.sqlString();
params.addAll(ret.parameters());
}
sql += ";";
try (ResultSet set = customQueryStatement(sql, params)) {
if (set.next()) {
return set.getLong(1);
}
}
} catch (SQLException e) {
throw new DBException(e);
}
throw new DBException("Cant retrieve element count from database (The ResultSet may be empty)");
}
public static ResultSet customQueryStatement(String sql, List<Object> params) throws DBException {
try {
PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql);
int i = 1;
for (Object val : params) {
if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
ps.setObject(i++, val);
}
Log.debug(ps.toString());
ResultSet rs = ps.executeQuery();
ps.closeOnCompletion();
return rs;
} catch (SQLException e) {
throw new DBException(e);
}
}
public static <E extends SQLElement<E>> SQLUpdate<E> update(Class<E> elemClass, SQLWhere<E> 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 DBException {
return new SQLUpdate<>(elemClass, where, values).execute();
}
/**
* Delete the elements of the table represented by {@code elemClass} which meet the condition {@code where}.
* @param elemClass the SQLElement representing the table.
* @param where the condition to meet for an element to be deleted from the table. If null, the table is truncated using {@link #truncateTable(Class)}.
* @return The return value of {@link PreparedStatement#executeUpdate()}, for an SQL query {@code DELETE}.
*/
public static <E extends SQLElement<E>> int delete(Class<E> elemClass, SQLWhere<E> where) throws DBException {
initTable(elemClass);
if (where == null) {
return truncateTable(elemClass);
}
ParameterizedSQLString whereData = where.toSQL();
String sql = "DELETE FROM " + getTableName(elemClass)
+ " WHERE " + whereData.sqlString()
+ ";";
List<Object> params = new ArrayList<>(whereData.parameters());
return customUpdateStatement(sql, params);
}
public static int customUpdateStatement(String sql, List<Object> params) throws DBException {
try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql)) {
int i = 1;
for (Object val : params) {
if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
ps.setObject(i++, val);
}
Log.debug(ps.toString());
return ps.executeUpdate();
} catch (SQLException e) {
throw new DBException(e);
}
}
public static <E extends SQLElement<E>> int truncateTable(Class<E> elemClass) throws DBException {
try (Statement stmt = connection.getNativeConnection().createStatement()) {
return stmt.executeUpdate("TRUNCATE `" + getTableName(elemClass) + "`");
} catch(SQLException e) {
throw new DBException(e);
}
}
@SuppressWarnings("unchecked")
private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws DBException {
try {
E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id"));
int fieldCount = set.getMetaData().getColumnCount();
for (int c = 1; c <= fieldCount; c++) {
String fieldName = set.getMetaData().getColumnLabel(c);
// ignore when field is present in database but not handled by SQLElement instance
if (!instance.getFields().containsKey(fieldName)) continue;
SQLField<E, Object> sqlField = (SQLField<E, Object>) instance.getFields().get(fieldName);
boolean customType = sqlField.type instanceof SQLCustomType;
Object val = set.getObject(c,
(Class<?>)(customType ? ((SQLCustomType<?, ?>)sqlField.type).intermediateJavaType
: sqlField.type.getJavaType()));
if (val == null || set.wasNull()) {
instance.set(sqlField, null, false);
}
else {
if (customType) {
try {
val = ((SQLCustomType<Object, Object>)sqlField.type).dbToJavaConv.apply(val);
} catch (Exception e) {
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 +"'", e);
}
}
instance.set(sqlField, val, false);
// la valeur venant de la BDD est marqué comme "non modifié"
// dans l'instance car le constructeur de l'instance met
// tout les champs comme modifiés
instance.modifiedSinceLastSave.remove(sqlField.getName());
}
}
if (!instance.isValidForSave()) throw new DBException(
"This SQLElement representing a database entry is not valid for save : " + instance);
return instance;
} catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | SQLException e) {
throw new DBException("Can't instanciate " + elemClass.getName(), e);
}
}
private DB() {}
}

View File

@@ -1,83 +0,0 @@
package fr.pandacube.lib.core.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import fr.pandacube.lib.core.util.Log;
public class DBConnection {
private static final long CONNECTION_CHECK_TIMEOUT = 30000; // in ms
private Connection conn;
private final String url;
private final String login;
private final String pass;
private long timeOfLastCheck = 0;
public DBConnection(String host, int port, String dbname, String l, String p)
throws SQLException {
url = "jdbc:mysql://" + host + ":" + port + "/" + dbname
+ "?autoReconnect=true"
+ "&useUnicode=true"
+ "&useSSL=false"
+ "&characterEncoding=utf8"
+ "&characterSetResults=utf8"
+ "&character_set_server=utf8mb4"
+ "&character_set_connection=utf8mb4";
login = l;
pass = p;
connect();
}
private void checkConnection() throws SQLException {
if (!isConnected()) {
Log.info("Connection to the database lost. Trying to reconnect...");
close();
connect();
}
}
public boolean isConnected()
{
try {
if (conn.isClosed())
return false;
// avoid checking the connection everytime we want to do a db request
long now = System.currentTimeMillis();
if (timeOfLastCheck + CONNECTION_CHECK_TIMEOUT > now)
return true;
timeOfLastCheck = now;
if (conn.isValid(1))
return true;
try (ResultSet rs = conn.createStatement().executeQuery("SELECT 1;")) {
return rs != null && rs.next();
}
} catch (Exception e) {
return false;
}
}
public Connection getNativeConnection() throws SQLException {
checkConnection();
return conn;
}
private void connect() throws SQLException {
conn = DriverManager.getConnection(url, login, pass);
timeOfLastCheck = System.currentTimeMillis();
}
public void close() {
try {
conn.close();
} catch (Exception ignored) {}
}
}

View File

@@ -1,17 +0,0 @@
package fr.pandacube.lib.core.db;
public class DBException extends Exception {
public DBException(Throwable initCause) {
super(initCause);
}
public DBException(String message, Throwable initCause) {
super(message, initCause);
}
public DBException(String message) {
super(message);
}
}

View File

@@ -1,13 +0,0 @@
package fr.pandacube.lib.core.db;
public class DBInitTableException extends DBException {
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem, Throwable t) {
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"), t);
}
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem, String message) {
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null") + ": " + message);
}
}

View File

@@ -1,8 +0,0 @@
package fr.pandacube.lib.core.db;
import java.util.List;
public record ParameterizedSQLString(String sqlString, List<Object> parameters) {
}

View File

@@ -1,25 +0,0 @@
package fr.pandacube.lib.core.db;
import java.util.function.Function;
/**
* @param <IT> intermediate type, the type of the value transmitted to the JDBC
* @param <JT> Java type
*/
public class SQLCustomType<IT, JT> extends SQLType<JT> {
public final Class<IT> intermediateJavaType;
public final Function<IT, JT> dbToJavaConv;
public final Function<JT, IT> javaToDbConv;
/* package */ SQLCustomType(SQLType<IT> type, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) {
this(type.sqlDeclaration, type.getJavaType(), javaT, dbToJava, javaToDb);
}
/* package */ SQLCustomType(String sqlD, Class<IT> intermediateJavaT, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) {
super(sqlD, javaT);
intermediateJavaType = intermediateJavaT;
dbToJavaConv = dbToJava;
javaToDbConv = javaToDb;
}
}

View File

@@ -1,505 +0,0 @@
package fr.pandacube.lib.core.db;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.gson.JsonObject;
import fr.pandacube.lib.core.util.EnumUtil;
import fr.pandacube.lib.core.util.Json;
import fr.pandacube.lib.core.util.Log;
import java.lang.reflect.Modifier;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
public abstract class SQLElement<E extends SQLElement<E>> {
/** cache for fields for each subclass of SQLElement */
/* package */ static final Map<Class<? extends SQLElement<?>>, SQLFieldMap<? extends SQLElement<?>>> fieldsCache = new HashMap<>();
private final DBConnection db = DB.getConnection();
private boolean stored = false;
private int id;
private final SQLFieldMap<E> fields;
private final Map<SQLField<E, ?>, Object> values;
/* package */ final Set<String> modifiedSinceLastSave;
@SuppressWarnings("unchecked")
public SQLElement() {
try {
DB.initTable((Class<E>)getClass());
} catch (DBInitTableException e) {
throw new RuntimeException(e);
}
if (fieldsCache.get(getClass()) == null) {
fields = new SQLFieldMap<>((Class<E>)getClass());
// le champ id commun à toutes les tables
SQLField<E, Integer> idF = new SQLField<>(INT, false, true, 0);
idF.setName("id");
fields.addField(idF);
generateFields(fields);
fieldsCache.put((Class<E>)getClass(), fields);
}
else
fields = (SQLFieldMap<E>) fieldsCache.get(getClass());
values = new LinkedHashMap<>(fields.size());
modifiedSinceLastSave = new HashSet<>(fields.size());
initDefaultValues();
}
protected SQLElement(int id) {
this();
@SuppressWarnings("unchecked")
SQLField<E, Integer> idField = (SQLField<E, Integer>) fields.get("id");
set(idField, id, false);
this.id = id;
stored = true;
}
/**
* @return The name of the table in the database, without the prefix defined by {@link DB#init(DBConnection, String)}.
*/
protected abstract String tableName();
@SuppressWarnings("unchecked")
private void initDefaultValues() {
// remplissage des données par défaut (si peut être null ou si valeur
// par défaut existe)
for (@SuppressWarnings("rawtypes")
SQLField f : fields.values())
if (f.defaultValue != null) set(f, f.defaultValue);
else if (f.canBeNull || (f.autoIncrement && !stored)) set(f, null);
}
@SuppressWarnings("unchecked")
protected void generateFields(SQLFieldMap<E> listToFill) {
java.lang.reflect.Field[] declaredFields = getClass().getDeclaredFields();
for (java.lang.reflect.Field field : declaredFields) {
if (!SQLField.class.isAssignableFrom(field.getType())) {
Log.debug("[ORM] The field " + field.getDeclaringClass().getName() + "." + field.getName() + " is of type " + field.getType().getName() + " so it will be ignored.");
continue;
}
if (!Modifier.isStatic(field.getModifiers())) {
Log.severe("[ORM] The field " + field.getDeclaringClass().getName() + "." + field.getName() + " can't be initialized because it is not static.");
continue;
}
field.setAccessible(true);
try {
Object val = field.get(null);
if (!(val instanceof SQLField)) {
Log.severe("[ORM] The field " + field.getDeclaringClass().getName() + "." + field.getName() + " can't be initialized because its value is null.");
continue;
}
SQLField<E, ?> checkedF = (SQLField<E, ?>) val;
checkedF.setName(field.getName());
if (!Modifier.isPublic(field.getModifiers()))
Log.warning("[ORM] The field " + field.getDeclaringClass().getName() + "." + field.getName() + " should be public !");
if (listToFill.containsKey(checkedF.getName())) throw new IllegalArgumentException(
"SQLField " + checkedF.getName() + " already exist in " + getClass().getName());
checkedF.setSQLElementType((Class<E>) getClass());
listToFill.addField((SQLField<?, ?>) val);
} catch (IllegalArgumentException | IllegalAccessException e) {
Log.severe("Can't get value of static field " + field, e);
}
}
}
/* package */ Map<String, SQLField<E, ?>> getFields() {
return Collections.unmodifiableMap(fields);
}
public Map<SQLField<E, ?>, Object> getValues() {
return Collections.unmodifiableMap(values);
}
@SuppressWarnings("unchecked")
public <T> E set(SQLField<E, T> field, T value) {
set(field, value, true);
return (E) this;
}
/* package */ <T> void set(SQLField<E, T> sqlField, T value, boolean setModified) {
if (sqlField == null) throw new IllegalArgumentException("sqlField can't be null");
if (!fields.containsValue(sqlField)) // should not append at runtime because of generic type check at compilation
throw new IllegalStateException("In the table "+getClass().getName()+ ": the field asked for modification is not initialized properly.");
if (value == null) {
if (!sqlField.canBeNull && (!sqlField.autoIncrement || stored))
throw new IllegalArgumentException(
"SQLField '" + sqlField.getName() + "' of " + getClass().getName() + " is a NOT NULL field");
}
else if (!sqlField.type.isInstance(value)) {
throw new IllegalArgumentException("SQLField '" + sqlField.getName() + "' of " + getClass().getName()
+ " type is '" + sqlField.type + "' and can't accept values of type "
+ value.getClass().getName());
}
if (!values.containsKey(sqlField)) {
values.put(sqlField, value);
if (setModified) modifiedSinceLastSave.add(sqlField.getName());
}
else {
Object oldVal = values.get(sqlField);
if (!Objects.equals(oldVal, value)) {
values.put(sqlField, value);
if (setModified) modifiedSinceLastSave.add(sqlField.getName());
}
// sinon, rien n'est modifié
}
}
public <T> T get(SQLField<E, T> field) {
if (field == null) throw new IllegalArgumentException("field can't be null");
if (values.containsKey(field)) {
@SuppressWarnings("unchecked")
T val = (T) values.get(field);
return val;
}
throw new IllegalArgumentException("The field '" + field.getName() + "' in this instance of " + getClass().getName()
+ " does not exist or is not set");
}
/**
* @param <T> the type of the specified 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.
*/
public <T, P extends SQLElement<P>> P getReferencedEntry(SQLFKField<E, T, P> field) throws DBException {
T fkValue = get(field);
if (fkValue == null) return null;
return DB.getFirst(field.getForeignElementClass(), field.getPrimaryField().eq(fkValue), null);
}
/**
* @param <T> the type of the specified field
* @param <F> the table class of the foreign key that reference a primary key of this element.
* @return all elements in the table F for which the specified foreign key value correspond to the primary key of this element.
*/
public <T, F extends SQLElement<F>> SQLElementList<F> getReferencingForeignEntries(SQLFKField<F, T, E> field, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws DBException {
T value = get(field.getPrimaryField());
if (value == null) return new SQLElementList<>();
return DB.getAll(field.getSQLElementType(), field.eq(value), orderBy, limit, offset);
}
public boolean isValidForSave() {
return values.keySet().containsAll(fields.values());
}
private Map<SQLField<E, ?>, Object> getOnlyModifiedValues() {
Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
values.forEach((k, v) -> {
if (modifiedSinceLastSave.contains(k.getName())) modifiedValues.put(k, v);
});
return modifiedValues;
}
public boolean isModified(SQLField<E, ?> field) {
return modifiedSinceLastSave.contains(field.getName());
}
@SuppressWarnings("unchecked")
public E save() throws DBException {
if (!isValidForSave())
throw new IllegalStateException(this + " has at least one undefined value and can't be saved.");
DB.initTable((Class<E>)getClass());
try {
if (stored) { // mettre à jour les valeurs dans la base
// restaurer l'ID au cas il aurait été changé à la main dans
// values
SQLField<E, Integer> idField = (SQLField<E, Integer>) fields.get("id");
values.put(idField, id);
modifiedSinceLastSave.remove("id");
Map<SQLField<E, ?>, Object> modifiedValues = getOnlyModifiedValues();
if (modifiedValues.isEmpty()) return (E) this;
DB.update((Class<E>)getClass(), getFieldId().eq(getId()), modifiedValues);
}
else { // ajouter dans la base
// restaurer l'ID au cas il aurait été changé à la main dans
// values
values.put(fields.get("id"), null);
StringBuilder concatValues = new StringBuilder();
StringBuilder concatFields = new StringBuilder();
List<Object> psValues = new ArrayList<>();
boolean first = true;
for (Map.Entry<SQLField<E, ?>, Object> entry : values.entrySet()) {
if (!first) {
concatValues.append(",");
concatFields.append(",");
}
first = false;
concatValues.append(" ? ");
concatFields.append("`").append(entry.getKey().getName()).append("`");
addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue());
}
try (PreparedStatement ps = db.getNativeConnection().prepareStatement(
"INSERT INTO " + DB.tablePrefix + tableName() + " (" + concatFields + ") VALUES (" + concatValues + ")",
Statement.RETURN_GENERATED_KEYS)) {
int i = 1;
for (Object val : psValues)
ps.setObject(i++, val);
ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) id = rs.getInt(1);
stored = true;
}
}
}
modifiedSinceLastSave.clear();
} catch (SQLException e) {
throw new DBException("Error while saving data", e);
}
return (E) this;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected static <E extends SQLElement<E>> void addValueToSQLObjectList(List<Object> list, SQLField<E, ?> field, Object jValue) throws DBException {
if (jValue != null && field.type instanceof SQLCustomType) {
try {
jValue = ((SQLCustomType)field.type).javaToDbConv.apply(jValue);
} catch (Exception e) {
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+"'", e);
}
}
list.add(jValue);
}
public boolean isStored() {
return stored;
}
public Integer getId() {
return (stored) ? id : null;
}
@SuppressWarnings("unchecked")
public SQLField<E, Integer> getFieldId() {
return (SQLField<E, Integer>) fields.get("id");
}
public void delete() throws DBException {
if (stored) { // supprimer la ligne de la base
try (PreparedStatement st = db.getNativeConnection()
.prepareStatement("DELETE FROM " + DB.tablePrefix + tableName() + " WHERE id=" + id)) {
Log.debug(st.toString());
st.executeUpdate();
markAsNotStored();
} catch (SQLException e) {
throw new DBException(e);
}
}
}
/**
* Méthode appelée quand l'élément courant est retirée de la base de données
* via une requête externe
*/
/* package */ void markAsNotStored() {
stored = false;
id = 0;
modifiedSinceLastSave.clear();
values.forEach((k, v) -> modifiedSinceLastSave.add(k.getName()));
}
protected static class SQLFieldMap<E extends SQLElement<E>> extends LinkedHashMap<String, SQLField<E, ?>> {
private final Class<E> sqlElemClass;
private SQLFieldMap(Class<E> elemClass) {
sqlElemClass = elemClass;
}
private void addField(SQLField<?, ?> f) {
if (f == null) return;
if (containsKey(f.getName())) throw new IllegalArgumentException(
"SQLField " + f.getName() + " already exist in " + sqlElemClass.getName());
@SuppressWarnings("unchecked")
SQLField<E, ?> checkedF = (SQLField<E, ?>) f;
checkedF.setSQLElementType(sqlElemClass);
put(checkedF.getName(), checkedF);
}
}
@Override
public String toString() {
ToStringHelper b = MoreObjects.toStringHelper(this);
for (SQLField<E, ?> f : fields.values())
try {
b.add(f.getName(), get(f));
} catch (IllegalArgumentException e) {
b.add(f.getName(), "(Undefined)");
}
return b.toString();
}
@Override
public boolean equals(Object o) {
if (!(getClass().isInstance(o))) return false;
SQLElement<?> oEl = (SQLElement<?>) o;
if (oEl.getId() == null) return false;
return oEl.getId().equals(getId());
}
@Override
public int hashCode() {
return getClass().hashCode() ^ Objects.hashCode(getId());
}
public JsonObject asJsonObject() {
JsonObject json = new JsonObject();
for (SQLField<E, ?> f : getFields().values()) {
json.add(f.getName(), Json.gson.toJsonTree(get(f)));
}
return json;
}
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul, boolean autoIncr, T deflt) {
return new SQLField<>(t, nul, autoIncr, deflt);
}
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul) {
return new SQLField<>(t, nul);
}
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul, boolean autoIncr) {
return new SQLField<>(t, nul, autoIncr);
}
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul, T deflt) {
return new SQLField<>(t, nul, deflt);
}
protected static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> foreignKeyId(boolean nul, Class<F> fkEl) {
return SQLFKField.idFK(nul, fkEl);
}
protected static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> foreignKeyId(boolean nul, Integer deflt, Class<F> fkEl) {
return SQLFKField.idFK(nul, deflt, fkEl);
}
protected static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> foreignKey(boolean nul, Class<F> fkEl, SQLField<F, T> fkF) {
return SQLFKField.customFK(nul, fkEl, fkF);
}
protected static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> foreignKey(boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) {
return SQLFKField.customFK(nul, deflt, fkEl, fkF);
}
public static final SQLType<Boolean> BOOLEAN = new SQLType<>("BOOLEAN", Boolean.class);
public static final SQLType<Integer> TINYINT = new SQLType<>("TINYINT", Integer.class); // cant be Byte due to MYSQL JDBC Connector limitations
public static final SQLType<Integer> BYTE = TINYINT;
public static final SQLType<Integer> SMALLINT = new SQLType<>("SMALLINT", Integer.class); // cant be Short due to MYSQL JDBC Connector limitations
public static final SQLType<Integer> SHORT = SMALLINT;
public static final SQLType<Integer> INT = new SQLType<>("INT", Integer.class);
public static final SQLType<Integer> INTEGER = INT;
public static final SQLType<Long> BIGINT = new SQLType<>("BIGINT", Long.class);
public static final SQLType<Long> LONG = BIGINT;
public static final SQLType<Date> DATE = new SQLType<>("DATE", Date.class);
public static final SQLType<Float> FLOAT = new SQLType<>("FLOAT", Float.class);
public static final SQLType<Double> DOUBLE = new SQLType<>("DOUBLE", Double.class);
public static SQLType<String> CHAR(int charCount) {
if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive.");
return new SQLType<>("CHAR(" + charCount + ")", String.class);
}
public static SQLType<String> VARCHAR(int charCount) {
if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive.");
return new SQLType<>("VARCHAR(" + charCount + ")", String.class);
}
public static final SQLType<String> TEXT = new SQLType<>("TEXT", String.class);
public static final SQLType<String> STRING = TEXT;
public static SQLType<byte[]> BINARY(int byteCount) {
if (byteCount <= 0) throw new IllegalArgumentException("byteCount must be positive.");
return new SQLType<>("BINARY(" + byteCount + ")", byte[].class);
}
public static SQLType<byte[]> VARBINARY(int byteCount) {
if (byteCount <= 0) throw new IllegalArgumentException("byteCount must be positive.");
return new SQLType<>("VARBINARY(" + byteCount + ")", byte[].class);
}
public static final SQLType<byte[]> BLOB = new SQLType<>("BLOB", byte[].class);
public static <T extends Enum<T>> SQLType<T> ENUM(Class<T> enumType) {
if (enumType == null) throw new IllegalArgumentException("enumType can't be null.");
StringBuilder enumStr = new StringBuilder("'");
boolean first = true;
for (T el : enumType.getEnumConstants()) {
if (!first) enumStr.append("', '");
first = false;
enumStr.append(el.name());
}
enumStr.append("'");
return new SQLCustomType<>("VARCHAR(" + enumStr + ")", String.class, enumType, s -> EnumUtil.searchEnum(enumType, s), Enum::name);
}
public static final SQLType<UUID> CHAR36_UUID = new SQLCustomType<>(CHAR(36), UUID.class, UUID::fromString, UUID::toString);
}

View File

@@ -1,196 +0,0 @@
package fr.pandacube.lib.core.db;
import com.google.gson.JsonArray;
import fr.pandacube.lib.core.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
*
* @param <E>
*/
public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
private final Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
@Override
public synchronized boolean add(E e) {
if (e == null || !e.isStored()) return false;
return super.add(e);
}
/**
* Défini une valeur à un champ qui sera appliquée dans la base de données à
* tous les
* entrées présente dans cette liste lors de l'appel à {@link #saveCommon()}
* .
* Les valeurs stockés dans chaque élément de cette liste ne seront affectés
* que lors de
* l'appel à {@link #saveCommon()}
*
* @param field le champs à modifier
* @param value la valeur à lui appliquer
*/
public synchronized <T> void setCommon(SQLField<E, T> field, T value) {
if (field == null)
throw new IllegalArgumentException("field can't be null");
if (Objects.equals(field.getName(), "id"))
throw new IllegalArgumentException("Can't modify id field in a SQLElementList");
Class<E> elemClass = field.getSQLElementType();
try {
E emptyElement = elemClass.getConstructor().newInstance();
emptyElement.set(field, value, false);
} catch (Exception e) {
throw new IllegalArgumentException("Illegal field or value or can't instanciante an empty instance of "
+ elemClass.getName() + ". (the instance is only created to test validity of field and value)", e);
}
// ici, la valeur est bonne
modifiedValues.put(field, value);
}
/**
* Applique toutes les valeurs défini avec
* {@link #setCommon(SQLField, Object)} à toutes
* les entrées dans la base de données correspondants aux entrées de cette
* liste. Les nouvelles
* valeurs sont aussi mises à jour dans les objets contenus dans cette
* liste, si la valeur n'a pas été modifiée individuellement avec
* {@link SQLElement#set(SQLField, Object)}.<br/>
* Les objets de cette liste qui n'ont pas leur données en base de données
* sont ignorées.
*/
public synchronized int saveCommon() throws DBException {
List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return 0;
@SuppressWarnings("unchecked")
Class<E> classEl = (Class<E>)storedEl.get(0).getClass();
int ret = DB.update(classEl,
storedEl.get(0).getFieldId().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList())
),
modifiedValues);
applyNewValuesToElements(storedEl);
return ret;
}
@SuppressWarnings("unchecked")
private void applyNewValuesToElements(List<E> storedEl) {
// applique les valeurs dans chaques objets de la liste
for (E el : storedEl)
for (@SuppressWarnings("rawtypes")
SQLField entry : modifiedValues.keySet())
if (!el.isModified(entry)) el.set(entry, modifiedValues.get(entry), false);
}
private List<E> getStoredEl() {
return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(ArrayList::new));
}
/**
* @deprecated please use {@link DB#delete(Class, SQLWhere)} instead,
* except if you really want to fetch the data before removing them from database.
*/
@Deprecated
public synchronized void removeFromDB() {
List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return;
try {
@SuppressWarnings("unchecked")
Class<E> classEl = (Class<E>)storedEl.get(0).getClass();
DB.delete(classEl,
storedEl.get(0).getFieldId().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList()))
);
for (E el : storedEl)
el.markAsNotStored();
} catch (DBException e) {
Log.severe(e);
}
}
public <T, P extends SQLElement<P>> SQLElementList<P> getReferencedEntries(SQLFKField<E, T, P> foreignKey, SQLOrderBy<P> orderBy) throws DBException {
Set<T> values = new HashSet<>();
forEach(v -> {
T val = v.get(foreignKey);
if (val != null)
values.add(val);
});
if (values.isEmpty()) {
return new SQLElementList<>();
}
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 DBException {
SQLElementList<P> foreignElemts = getReferencedEntries(foreignKey, null);
Map<T, P> ret = new HashMap<>();
foreignElemts.forEach(foreignVal -> ret.put(foreignVal.get(foreignKey.getPrimaryField()), foreignVal));
return ret;
}
public <T, F extends SQLElement<F>> SQLElementList<F> getReferencingForeignEntries(SQLFKField<F, T, E> foreignKey, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws DBException {
Set<T> values = new HashSet<>();
forEach(v -> {
T val = v.get(foreignKey.getPrimaryField());
if (val != null)
values.add(val);
});
if (values.isEmpty()) {
return new SQLElementList<>();
}
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 DBException {
SQLElementList<F> foreignElements = getReferencingForeignEntries(foreignKey, orderBy, limit, offset);
Map<T, SQLElementList<F>> map = new HashMap<>();
foreignElements.forEach(foreignVal -> {
SQLElementList<F> subList = map.getOrDefault(foreignVal.get(foreignKey), new SQLElementList<>());
subList.add(foreignVal);
map.put(foreignVal.get(foreignKey), subList);
});
return map;
}
public JsonArray asJsonArray() {
JsonArray json = new JsonArray();
forEach(el -> json.add(el.asJsonObject()));
return json;
}
}

View File

@@ -1,69 +0,0 @@
package fr.pandacube.lib.core.db;
import fr.pandacube.lib.core.util.Log;
/**
*
* @author Marc
*
* @param <F> the table class of this current foreign key field
* @param <T> the Java type of this field
* @param <P> the table class of the targeted primary key
*/
public class SQLFKField<F extends SQLElement<F>, T, P extends SQLElement<P>> extends SQLField<F, T> {
private SQLField<P, T> sqlPrimaryKeyField;
private Class<P> sqlForeignKeyElemClass;
protected SQLFKField(SQLType<T> t, boolean nul, T deflt, Class<P> fkEl, SQLField<P, T> fkF) {
super(t, nul, deflt);
construct(fkEl, fkF);
}
/* package */ static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Class<F> fkEl) {
return idFK(nul, null, 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");
try {
SQLField<F, Integer> f = DB.getSQLIdField(fkEl);
return new SQLFKField<>(f.type, nul, deflt, fkEl, f);
} catch (DBInitTableException e) {
Log.severe("Can't create Foreign key Field targetting id field of '"+fkEl+"'", e);
return null;
}
}
/* package */ static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> customFK(boolean nul, Class<F> fkEl, SQLField<F, T> fkF) {
return customFK(nul, null, fkEl, fkF);
}
/* package */ static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> customFK(boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) {
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
return new SQLFKField<>(fkF.type, nul, deflt, fkEl, fkF);
}
private void construct(Class<P> fkEl, SQLField<P, T> fkF) {
if (fkF == null) throw new IllegalArgumentException("foreignKeyField can't be null");
try {
DB.initTable(fkEl);
} catch (DBInitTableException e) {
throw new RuntimeException(e);
}
if (fkF.getSQLElementType() == null)
throw new RuntimeException("Can't initialize foreign key. The primary key in the table " + fkEl.getName() + " is not properly initialized and can't be targetted by a forein key");
sqlPrimaryKeyField = fkF;
sqlForeignKeyElemClass = fkEl;
}
public SQLField<P, T> getPrimaryField() {
return sqlPrimaryKeyField;
}
public Class<P> getForeignElementClass() {
return sqlForeignKeyElemClass;
}
}

View File

@@ -1,140 +0,0 @@
package fr.pandacube.lib.core.db;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import fr.pandacube.lib.core.db.SQLWhere.SQLWhereComp;
import fr.pandacube.lib.core.db.SQLWhere.SQLWhereComp.SQLComparator;
import fr.pandacube.lib.core.db.SQLWhere.SQLWhereIn;
import fr.pandacube.lib.core.db.SQLWhere.SQLWhereLike;
import fr.pandacube.lib.core.db.SQLWhere.SQLWhereNull;
public class SQLField<E extends SQLElement<E>, T> {
private Class<E> sqlElemClass;
private String name = null;
public final SQLType<T> type;
public final boolean canBeNull;
public final boolean autoIncrement;
/* package */ final T defaultValue;
/* package */ SQLField(SQLType<T> t, boolean nul, boolean autoIncr, T deflt) {
type = t;
canBeNull = nul;
autoIncrement = autoIncr;
defaultValue = deflt;
}
/* package */ SQLField(SQLType<T> t, boolean nul) {
this(t, nul, false, null);
}
/* package */ SQLField(SQLType<T> t, boolean nul, boolean autoIncr) {
this(t, nul, autoIncr, null);
}
/* package */ SQLField(SQLType<T> t, boolean nul, T deflt) {
this(t, nul, false, deflt);
}
/* package */ ParameterizedSQLString forSQLPreparedStatement() {
List<Object> params = new ArrayList<>(1);
if (defaultValue != null && !autoIncrement) params.add(defaultValue);
return new ParameterizedSQLString("`" + getName() + "` " + type.toString() + (canBeNull ? " NULL" : " NOT NULL")
+ (autoIncrement ? " AUTO_INCREMENT" : "")
+ ((defaultValue == null || autoIncrement) ? "" : " DEFAULT ?"), params);
}
/* package */ void setSQLElementType(Class<E> elemClass) {
sqlElemClass = elemClass;
}
public Class<E> getSQLElementType() {
return sqlElemClass;
}
/* package */ void setName(String n) {
name = n;
}
public String getName() {
return name;
}
/**
* <b>Don't use this {@code toString()} method in a SQL query, because
* the default value is not escaped correctly</b>
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return forSQLPreparedStatement().sqlString().replaceFirst("\\?",
(defaultValue != null && !autoIncrement) ? defaultValue.toString() : "");
}
@Override
public boolean equals(Object obj) {
return obj instanceof SQLField<?, ?> f
&& f.getName().equals(getName())
&& f.sqlElemClass.equals(sqlElemClass);
}
@Override
public int hashCode() {
return getName().hashCode() + sqlElemClass.hashCode();
}
public SQLWhere<E> eq(T r) {
return comp(SQLComparator.EQ, r);
}
public SQLWhere<E> geq(T r) {
return comp(SQLComparator.GEQ, r);
}
public SQLWhere<E> gt(T r) {
return comp(SQLComparator.GT, r);
}
public SQLWhere<E> leq(T r) {
return comp(SQLComparator.LEQ, r);
}
public SQLWhere<E> lt(T r) {
return comp(SQLComparator.LT, r);
}
public SQLWhere<E> neq(T r) {
return comp(SQLComparator.NEQ, r);
}
private SQLWhere<E> comp(SQLComparator c, T r) {
if (r == null)
throw new IllegalArgumentException("The value cannot be null. Use SQLField#isNull(value) or SQLField#isNotNull(value) to check for null values");
return new SQLWhereComp<>(this, c, r);
}
public SQLWhere<E> like(String like) {
return new SQLWhereLike<>(this, like);
}
public SQLWhere<E> in(Collection<T> v) {
return new SQLWhereIn<>(this, v);
}
public SQLWhere<E> isNull() {
return new SQLWhereNull<>(this, true);
}
public SQLWhere<E> isNotNull() {
return new SQLWhereNull<>(this, false);
}
}

View File

@@ -1,93 +0,0 @@
package fr.pandacube.lib.core.db;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class SQLOrderBy<E extends SQLElement<E>> {
private final List<OBField> orderByFields = new ArrayList<>();
/**
* Construit une nouvelle clause ORDER BY
*/
private SQLOrderBy() {}
/**
* Ajoute un champ dans la clause ORDER BY en construction
*
* @param field le champ SQL à ordonner
* @param d le sens de tri (croissant ASC ou décroissant DESC)
* @return l'objet courant (permet de chainer les ajouts de champs)
*/
private SQLOrderBy<E> add(SQLField<E, ?> field, Direction d) {
orderByFields.add(new OBField(field, d));
return this;
}
/**
* Ajoute un champ dans la clause ORDER BY en construction avec pour direction ASC
*
* @param field le champ SQL à ordonner
* @return l'objet courant (permet de chainer les ajouts de champs)
*/
public SQLOrderBy<E> thenAsc(SQLField<E, ?> field) {
return add(field, Direction.ASC);
}
/**
* Ajoute un champ dans la clause ORDER BY en construction avec pour direction DESC
*
* @param field le champ SQL à ordonner
* @return l'objet courant (permet de chainer les ajouts de champs)
*/
public SQLOrderBy<E> thenDesc(SQLField<E, ?> field) {
return add(field, Direction.DESC);
}
/* package */ String toSQL() {
return orderByFields.stream()
.map(f -> "`" + f.field.getName() + "` " + f.direction.name())
.collect(Collectors.joining(", "));
}
@Override
public String toString() {
return toSQL();
}
private class OBField {
public final SQLField<E, ?> field;
public final Direction direction;
public OBField(SQLField<E, ?> f, Direction d) {
field = f;
direction = d;
}
}
private enum Direction {
ASC, DESC
}
public static <E extends SQLElement<E>> SQLOrderBy<E> asc(SQLField<E, ?> field) {
return new SQLOrderBy<E>().thenAsc(field);
}
public static <E extends SQLElement<E>> SQLOrderBy<E> desc(SQLField<E, ?> field) {
return new SQLOrderBy<E>().thenDesc(field);
}
}

View File

@@ -1,38 +0,0 @@
package fr.pandacube.lib.core.db;
public class SQLType<T> {
protected final String sqlDeclaration;
private final Class<T> javaTypes;
/* package */ SQLType(String sqlD, Class<T> javaT) {
sqlDeclaration = sqlD;
javaTypes = javaT;
}
@Override
public String toString() {
return sqlDeclaration;
}
public boolean isInstance(Object val) {
return javaTypes.isInstance(val);
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof SQLType o
&& toString().equals(o.toString());
}
public Class<T> getJavaType() {
return javaTypes;
}
}

View File

@@ -1,68 +0,0 @@
package fr.pandacube.lib.core.db;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import fr.pandacube.lib.core.util.Log;
public class SQLUpdate<E extends SQLElement<E>> {
private final Class<E> elemClass;
private final SQLWhere<E> where;
private final Map<SQLField<E, ?>, Object> values;
/* package */ SQLUpdate(Class<E> el, SQLWhere<E> w) {
elemClass = el;
where = w;
values = new HashMap<>();
}
/* package */ SQLUpdate(Class<E> el, SQLWhere<E> w, Map<SQLField<E, ?>, Object> v) {
elemClass = el;
where = w;
values = v;
}
public <T> SQLUpdate<E> set(SQLField<E, T> field, T value) {
values.put(field, value);
return this;
}
public SQLUpdate<E> setUnsafe(SQLField<E, ?> field, Object value) {
values.put(field, value);
return this;
}
public int execute() throws DBException {
if (values.isEmpty()) {
Log.warning(new DBException("Trying to do an UPDATE with no values to SET. Query aborted."));
return 0;
}
StringBuilder sql = new StringBuilder("UPDATE " + DB.getTableName(elemClass) + " SET ");
List<Object> params = new ArrayList<>();
boolean first = true;
for (Map.Entry<SQLField<E, ?>, Object> entry : values.entrySet()) {
if (!first)
sql.append(", ");
sql.append("`").append(entry.getKey().getName()).append("` = ? ");
SQLElement.addValueToSQLObjectList(params, entry.getKey(), entry.getValue());
first = false;
}
if (where != null) {
ParameterizedSQLString ret = where.toSQL();
sql.append(" WHERE ").append(ret.sqlString());
params.addAll(ret.parameters());
}
sql.append(";");
return DB.customUpdateStatement(sql.toString(), params);
}
}

View File

@@ -1,309 +0,0 @@
package fr.pandacube.lib.core.db;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import fr.pandacube.lib.core.util.Log;
public abstract class SQLWhere<E extends SQLElement<E>> {
public abstract ParameterizedSQLString toSQL() throws DBException;
@Override
public String toString() {
try {
return toSQL().sqlString();
} 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 final SQLBoolOp operator;
private final 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);
}
public boolean isEmpty() {
return conditions.isEmpty();
}
@Override
public ParameterizedSQLString toSQL() throws DBException {
if (conditions.isEmpty()) {
throw new DBException("SQLWhereChain needs at least one element inside !");
}
StringBuilder sql = new StringBuilder();
List<Object> params = new ArrayList<>();
boolean first = true;
for (SQLWhere<E> w : conditions) {
if (!first)
sql.append(" ").append(operator.sql).append(" ");
first = false;
ParameterizedSQLString ret = w.toSQL();
sql.append("(").append(ret.sqlString()).append(")");
params.addAll(ret.parameters());
}
return new ParameterizedSQLString(sql.toString(), params);
}
protected enum SQLBoolOp {
/** Equivalent to SQL "<code>AND</code>" */
AND("AND"),
/** Equivalent to SQL "<code>OR</code>" */
OR("OR");
/* package */ final String sql;
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 final SQLField<E, ?> left;
private final SQLComparator comp;
private final 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 ParameterizedSQLString toSQL() throws DBException {
List<Object> params = new ArrayList<>();
SQLElement.addValueToSQLObjectList(params, left, right);
return new ParameterizedSQLString("`" + left.getName() + "` " + comp.sql + " ? ", params);
}
/* package */ enum SQLComparator {
/** Equivalent to SQL "<code>=</code>" */
EQ("="),
/** Equivalent to SQL "<code>></code>" */
GT(">"),
/** Equivalent to SQL "<code>>=</code>" */
GEQ(">="),
/** Equivalent to SQL "<code>&lt;</code>" */
LT("<"),
/** Equivalent to SQL "<code>&lt;=</code>" */
LEQ("<="),
/** Equivalent to SQL "<code>!=</code>" */
NEQ("!=");
/* package */ final String sql;
SQLComparator(String s) {
sql = s;
}
}
}
/* package */ static class SQLWhereIn<E extends SQLElement<E>> extends SQLWhere<E> {
private final SQLField<E, ?> field;
private final 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 ParameterizedSQLString toSQL() throws DBException {
List<Object> params = new ArrayList<>();
if (values.isEmpty())
return new ParameterizedSQLString(" 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 ParameterizedSQLString("`" + field.getName() + "` IN (" + new String(questions) + ") ", params);
}
}
/* package */ static class SQLWhereLike<E extends SQLElement<E>> extends SQLWhere<E> {
private final SQLField<E, ?> field;
private final 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 ParameterizedSQLString toSQL() {
ArrayList<Object> params = new ArrayList<>();
params.add(likeExpr);
return new ParameterizedSQLString("`" + field.getName() + "` LIKE ? ", params);
}
}
/* package */ static class SQLWhereNull<E extends SQLElement<E>> extends SQLWhere<E> {
private final SQLField<E, ?> field;
private final boolean isNull;
/**
* 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'");
this.field = field;
this.isNull = isNull;
}
@Override
public ParameterizedSQLString toSQL() {
return new ParameterizedSQLString("`" + field.getName() + "` IS " + ((isNull) ? "NULL" : "NOT NULL"), new ArrayList<>());
}
}
}

View File

@@ -1,62 +0,0 @@
package fr.pandacube.lib.core.net;
import java.util.Arrays;
public class Array8Bit {
public static final int BIT_COUNT = Byte.SIZE;
private boolean[] values = new boolean[BIT_COUNT];
public Array8Bit(byte b) {
fromByte(b);
}
/**
* @param bits (index 0 is the lowest significant bit)
*/
public Array8Bit(boolean[] bits) {
if (bits == null || bits.length != BIT_COUNT)
throw new IllegalArgumentException("bits is null or bits.length != "+BIT_COUNT);
values = Arrays.copyOf(bits, BIT_COUNT);
}
/**
* i = 0 is the lowest significant bit
*/
public boolean getBit(int i) {
return values[i];
}
/**
* i = 0 is the lowest significant bit
*/
public void setBit(int i, boolean b) {
values[i] = b;
}
public void fromByte(byte b) {
int mask = 1;
for (int i = 0; i < BIT_COUNT; i++) {
values[i] = (b & mask) != 0;
mask <<= 1;
}
}
public byte toByte() {
byte b = 0;
for (int i=BIT_COUNT-1; i>=0; i--) {
b <<= 1;
if (values[i]) b |= 1;
}
return b;
}
}

View File

@@ -1,271 +0,0 @@
package fr.pandacube.lib.core.net;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final class ByteBuffer implements Cloneable {
public static final Charset NETWORK_CHARSET = StandardCharsets.UTF_8;
private java.nio.ByteBuffer buff;
public ByteBuffer() {
this(16);
}
public ByteBuffer(int initSize) {
buff = java.nio.ByteBuffer.allocate(initSize);
}
/**
* Create a ByteBuffer that is initially <b>backed</b> by the provided byte array.
* The position of this buffer will be 0.
* If this ByteBuffer needs a biffer array, the provided array is replaced by a new one,
* making the provided array not related to this ByteBuffer anymore.
* @param data array of byte that serve as a backend for this ByteBuffer.
*/
public ByteBuffer(byte[] data) {
buff = java.nio.ByteBuffer.wrap(data);
}
private void askForBufferExtension(int needed) {
while (buff.remaining() < needed) {
java.nio.ByteBuffer newBuff = java.nio.ByteBuffer.wrap(Arrays.copyOf(buff.array(), buff.array().length * 2));
newBuff.position(buff.position());
buff = newBuff;
}
}
/**
* This clone method also clone the underlying array.
*/
@Override
public ByteBuffer clone() {
return new ByteBuffer(Arrays.copyOf(buff.array(), buff.array().length));
}
/**
* @see java.nio.ByteBuffer#get()
*/
public byte getByte() {
return buff.get();
}
/**
* @see java.nio.ByteBuffer#get(byte[])
*/
public byte[] getByteArray(byte[] b) {
buff.get(b);
return b;
}
/**
* Return the next byte array wich is preceded with his size as integer,
* or null if the founded size is negative.
*/
public byte[] getSizedByteArray() {
int size = getInt();
if (size < 0) return null;
return getByteArray(new byte[size]);
}
/**
* @see java.nio.ByteBuffer#getChar()
*/
public char getChar() {
return buff.getChar();
}
/**
* @see java.nio.ByteBuffer#getShort()
*/
public short getShort() {
return buff.getShort();
}
/**
* @see java.nio.ByteBuffer#getInt()
*/
public int getInt() {
return buff.getInt();
}
/**
* @see java.nio.ByteBuffer#getLong()
*/
public long getLong() {
return buff.getLong();
}
/**
* @see java.nio.ByteBuffer#getFloat()
*/
public float getFloat() {
return buff.getFloat();
}
/**
* @see java.nio.ByteBuffer#getDouble()
*/
public double getDouble() {
return buff.getDouble();
}
/**
* @see java.nio.ByteBuffer#put(byte)
*/
public ByteBuffer putByte(byte b) {
askForBufferExtension(Byte.BYTES);
buff.put(b);
return this;
}
/**
* @see java.nio.ByteBuffer#put(byte[])
*/
public ByteBuffer putByteArray(byte[] b) {
askForBufferExtension(b.length * Byte.BYTES);
buff.put(b);
return this;
}
public ByteBuffer putSizedByteArray(byte[] b) {
if (b == null) {
return putInt(-1);
}
putInt(b.length);
return putByteArray(b);
}
/**
* @see java.nio.ByteBuffer#putChar(char)
*/
public ByteBuffer putChar(char value) {
askForBufferExtension(Character.BYTES);
buff.putChar(value);
return this;
}
/**
* @see java.nio.ByteBuffer#putShort(short)
*/
public ByteBuffer putShort(short value) {
askForBufferExtension(Short.BYTES);
buff.putShort(value);
return this;
}
/**
* @see java.nio.ByteBuffer#putInt(int)
*/
public ByteBuffer putInt(int value) {
askForBufferExtension(Integer.BYTES);
buff.putInt(value);
return this;
}
/**
* @see java.nio.ByteBuffer#putLong(long)
*/
public ByteBuffer putLong(long value) {
askForBufferExtension(Long.BYTES);
buff.putLong(value);
return this;
}
/**
* @see java.nio.ByteBuffer#putFloat(float)
*/
public ByteBuffer putFloat(float value) {
askForBufferExtension(Float.BYTES);
buff.putFloat(value);
return this;
}
/**
* @see java.nio.ByteBuffer#putDouble(double)
*/
public ByteBuffer putDouble(double value) {
askForBufferExtension(Double.BYTES);
buff.putDouble(value);
return this;
}
/**
* @see java.nio.ByteBuffer#position()
*/
public int getPosition() {
return buff.position();
}
/**
* @see java.nio.ByteBuffer#position(int)
*/
public void setPosition(int p) {
buff.position(p);
}
/**
* @see java.nio.ByteBuffer#capacity()
*/
public int capacity() {
return buff.capacity();
}
/**
*
* @param s null String are supported
*/
public ByteBuffer putString(String s) {
if (s == null) {
return putInt(-1);
}
return putSizedByteArray(s.getBytes(NETWORK_CHARSET));
}
/**
* returned string can be null
*/
public String getString() {
byte[] binaryString = getSizedByteArray();
return (binaryString == null) ? null : new String(binaryString, NETWORK_CHARSET);
}
/**
*
* @param list The list can be null, and any String can be null too.
*/
public ByteBuffer putListOfString(List<String> list) {
if (list == null) {
return putInt(-1);
}
putInt(list.size());
for (String str : list)
putString(str);
return this;
}
/**
* @return a List of String. The list can be null, and any element can be null too.
*/
public List<String> getListOfString() {
int size = getInt();
if (size < 0)
return null;
List<String> list = new ArrayList<>();
for (int i = 0; i < size; i++)
list.add(getString());
return list;
}
/**
* @see java.nio.ByteBuffer#array()
*/
public byte[] array() {
return buff.array();
}
}

View File

@@ -1,57 +0,0 @@
package fr.pandacube.lib.core.net;
import java.util.Arrays;
public class PPacket {
public final String name;
/* package */ int id;
public final byte[] content;
/**
* Construct a new PPacket based on the content of the provided buffer before his position.
* @param n the name of the packet.
* @param buff the buffer where the data comes from. Only the content before {@link ByteBuffer#getPosition()} is copied.
*/
public PPacket(String n, ByteBuffer buff) {
this(n, Arrays.copyOf(buff.array(), buff.getPosition()));
}
public PPacket(String n, byte[] c) {
name = n;
content = c;
}
/* package */ PPacket(String n, int i, byte[] c) {
this(n, c);
id = i;
}
public ByteBuffer getContentAsBuffer() {
return new ByteBuffer(content);
}
public static PPacket buildSingleStringContentPacket(String name, String content) {
return new PPacket(name, new ByteBuffer().putString(content));
}
/* package */ static PPacket buildLoginPacket(String password) {
return buildSingleStringContentPacket("login", password);
}
/* package */ static PPacket buildBadFormatPacket(String message) {
return buildSingleStringContentPacket("bad_format", message);
}
/* package */ static PPacket buildLoginBadPacket() {
return new PPacket("login_bad", new byte[0]);
}
}

View File

@@ -1,44 +0,0 @@
package fr.pandacube.lib.core.net;
import java.util.Arrays;
public class PPacketAnswer extends PPacket {
/* package */ final int answer;
/**
* Construct a new PPacketAnswer based on the content of the provided buffer before his position.
* @param n the name of the packet.
* @param buff the buffer where the data comes from. Only the content before {@link ByteBuffer#getPosition()} is copied.
*/
public PPacketAnswer(PPacket answered, String n, ByteBuffer buff) {
this(answered, n, Arrays.copyOf(buff.array(), buff.getPosition()));
}
public PPacketAnswer(PPacket answered, String n, byte[] c) {
super(n, c);
answer = answered.id;
}
/* package */ PPacketAnswer(String n, int i, int a, byte[] c) {
super(n, i, c);
answer = a;
}
public static PPacketAnswer buildSingleStringContentPacketAnswer(PPacket answered, String name, String content) {
ByteBuffer pwBuff = new ByteBuffer().putString(content);
return new PPacketAnswer(answered, name, Arrays.copyOf(pwBuff.array(), pwBuff.getPosition()));
}
/* package */ static PPacketAnswer buildLoginOkPacket(PPacket loginPacket) {
return new PPacketAnswer(loginPacket, "login_ok", new byte[0]);
}
/* package */ static PPacketAnswer buildExceptionPacket(PPacket answered, String message) {
return buildSingleStringContentPacketAnswer(answered, "exception", message);
}
}

View File

@@ -1,13 +0,0 @@
package fr.pandacube.lib.core.net;
@FunctionalInterface
public interface PPacketListener<P extends PPacket> {
/**
* Called when we receive a packet (except responses)
* @param connection the connection from where the packet comes
* @param packet the received packet
*/
void onPacketReceive(PSocket connection, P packet);
}

View File

@@ -1,159 +0,0 @@
package fr.pandacube.lib.core.net;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.base.MoreObjects;
import fr.pandacube.lib.core.util.Log;
public class PServer extends Thread implements Closeable {
private static final AtomicInteger connectionCounterId = new AtomicInteger(0);
private final int port;
private ServerSocket socket;
private final String socketName;
private final List<TCPServerClientConnection> clients = Collections.synchronizedList(new ArrayList<>());
private final AtomicBoolean isClosed = new AtomicBoolean(false);
private final List<PPacketListener<PPacket>> globalPacketListeners = Collections.synchronizedList(new ArrayList<>());
private final List<PSocketConnectionListener> clientConnectionListeners = Collections.synchronizedList(new ArrayList<>());
private final String password;
public PServer(int port, String sckName, String password) {
super("PServer " + sckName);
setDaemon(true);
if (port <= 0 || port > 65535) throw new IllegalArgumentException("le numéro de port est invalide");
socketName = sckName;
this.port = port;
this.password = password;
}
@Override
public void run() {
try {
socket = new ServerSocket();
socket.setReceiveBufferSize(PSocket.NETWORK_TCP_BUFFER_SIZE);
socket.setPerformancePreferences(0, 1, 0);
socket.bind(new InetSocketAddress(port));
while (true) {
Socket socketClient = socket.accept();
socketClient.setSendBufferSize(PSocket.NETWORK_TCP_BUFFER_SIZE);
socketClient.setSoTimeout(PSocket.NETWORK_TIMEOUT);
TCPServerClientConnection co = new TCPServerClientConnection(socketClient,
connectionCounterId.getAndIncrement());
co.start();
}
} catch (SocketException ignored) {
} catch (Exception e) {
Log.warning("Plus aucune connexion ne peux être acceptée", e);
}
}
public void addPacketListener(PPacketListener<PPacket> l) {
globalPacketListeners.add(l);
}
public boolean removePacketListener(PPacketListener<PPacket> l) {
return globalPacketListeners.remove(l);
}
public void addConnectionListener(PSocketConnectionListener l) {
clientConnectionListeners.add(l);
}
public void removeConnectionListener(PSocketConnectionListener l) {
clientConnectionListeners.remove(l);
}
protected class TCPServerClientConnection extends PSocket {
boolean loggedIn;
private TCPServerClientConnection(Socket s, int coId) {
super(s, "Conn#" + coId + " via TCPSv " + socketName, password);
addConnectionListener(new PSocketConnectionListener() {
@Override
public void onDisconnect(PSocket connection) {
try {
clientConnectionListeners.forEach(l -> l.onDisconnect(connection));
} finally {
clients.remove((TCPServerClientConnection)connection);
}
}
@Override
public void onConnect(PSocket connection) {
clients.add((TCPServerClientConnection)connection);
clientConnectionListeners.forEach(l -> l.onConnect(connection));
}
});
addPacketListener((conn, packet) ->
globalPacketListeners.forEach(l -> {
try {
l.onPacketReceive(conn, packet);
} catch (Exception e) {
Log.severe("Exception while calling PPacketListener.onPacketReceive().", e);
sendSilently(PPacketAnswer.buildExceptionPacket(packet, e.toString()));
}
})
);
}
}
@Override
public void close() {
try {
if (isClosed.get()) return;
isClosed.set(true);
clients.forEach(PSocket::close);
socket.close();
} catch (IOException ignored) {}
}
public boolean isClosed() {
return isClosed.get() || socket.isClosed();
}
public List<PSocket> getClients() {
synchronized (clients) {
return new ArrayList<>(clients);
}
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("thread", getName())
.add("socket", socket)
.toString();
}
}

View File

@@ -1,350 +0,0 @@
package fr.pandacube.lib.core.net;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import fr.pandacube.lib.core.util.Log;
/**
* A wrapper for a {@link Socket}. The connection must point to a software using {@link PServer}
* as wrapper for the target {@link ServerSocket}.
* <br>
* This class provides a simple way to exchange data between client and server :
* <li>Maintained connection with the server</li>
* <li>Login with a password (send in the first packet)</li>
* <li>Named packets</li>
* <li>Binary data</li>
* <li>Input stream in a separate Thread</li>
*
*/
public class PSocket extends Thread implements Closeable {
public static final int NETWORK_TCP_BUFFER_SIZE = 1024 * 1024;
public static final int NETWORK_TIMEOUT = 0; // no timeout (milli-seconds)
private boolean server = false;
private Socket socket;
private final SocketAddress addr;
private DataInputStream in;
private DataOutputStream out;
private final Object outSynchronizer = new Object();
private String password;
private final AtomicBoolean isClosed = new AtomicBoolean(false);
private final List<PPacketListener<PPacket>> packetListeners = Collections.synchronizedList(new ArrayList<>());
private final List<PSocketConnectionListener> connectionListeners = Collections.synchronizedList(new ArrayList<>());
private final Map<Integer, PPacketListener<PPacketAnswer>> answersCallbacks = Collections.synchronizedMap(new HashMap<>());
private int nextSendId = 0;
/**
* Create a new PSocket that will connect to the specified SocketAddress.
* @param a The target server to connect to
* @param connName the name of the connection, used to name the Thread used to receive the packet.
* @param pass the password to send to the server.
*/
public PSocket(SocketAddress a, String connName, String pass) {
super("PSocket " + connName);
setDaemon(true);
if (a == null) throw new IllegalArgumentException("les arguments ne peuvent pas être null");
addr = a;
}
/* package */ PSocket(Socket s, String connName, String pass) {
this(s.getRemoteSocketAddress(), connName, pass);
socket = s;
server = true;
}
@Override
public void run() {
try {
if (socket == null) {
socket = new Socket();
socket.setReceiveBufferSize(NETWORK_TCP_BUFFER_SIZE);
socket.setSendBufferSize(NETWORK_TCP_BUFFER_SIZE);
socket.setSoTimeout(10000); // initial timeout before login
socket.connect(addr);
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
}
// password check
if (server) {
PPacket packet = readPacket();
if (packet == null || packet instanceof PPacketAnswer || !"login".equals(packet.name)) {
send(PPacket.buildLoginBadPacket());
close();
return;
}
try {
String receivedPassword = new ByteBuffer(packet.content).getString();
if (!Objects.equal(receivedPassword, password)) {
send(PPacket.buildLoginBadPacket());
close();
return;
}
} catch(Exception e) {
send(PPacket.buildLoginBadPacket());
close();
return;
}
send(PPacketAnswer.buildLoginOkPacket(packet));
// login ok at this point
}
else {
send(PPacket.buildLoginPacket(password));
PPacket packet = readPacket();
if (packet == null) {
Log.severe("bad packet received from server. Disconnecting.");
close();
return;
}
if (packet.name.equals("login_bad")) {
Log.severe("Wrong password to connect to server. Disconnecting.");
close();
return;
}
if (!packet.name.equals("login_ok")) {
Log.severe("Unexpected packet from server. Disconnecting.");
close();
return;
}
// login ok at this point
}
password = null;
socket.setSoTimeout(NETWORK_TIMEOUT);
Log.info(getName() + " connected.");
connectionListeners.forEach(l -> {
try {
l.onConnect(this);
} catch (Exception e) {
Log.severe("Exception while calling PSocketConnectionListener.onConnect().", e);
}
});
while (!socket.isClosed()) {
PPacket packet = readPacket();
if (packet == null) {
send(PPacket.buildBadFormatPacket("Bad format for the last packet received. Closing connection."));
break;
}
if (packet instanceof PPacketAnswer) {
try {
answersCallbacks.remove(((PPacketAnswer)packet).answer).onPacketReceive(this, (PPacketAnswer)packet);
} catch (Exception e) {
Log.severe("Exception while calling PPacketListener.onPacketReceive().", e);
send(PPacketAnswer.buildExceptionPacket(packet, e.toString()));
}
}
else {
packetListeners.forEach(l -> {
try {
l.onPacketReceive(this, packet);
} catch (Exception e) {
Log.severe("Exception while calling PPacketListener.onPacketReceive().", e);
sendSilently(PPacketAnswer.buildExceptionPacket(packet, e.toString()));
}
});
}
}
} catch (Exception e) {
Log.severe(e);
}
close();
}
/**
* Return the packet read in the socket, or null if the packet is in a bad format.
* @return the packet
*
*/
private PPacket readPacket() throws IOException {
byte nSize = in.readByte();
if (nSize == 0) {
return null;
}
boolean answer = nSize < 0;
if (answer)
nSize *= -1;
byte[] nBytes = new byte[nSize];
in.readFully(nBytes);
String name = new String(nBytes, ByteBuffer.NETWORK_CHARSET);
int packetId = in.readInt();
int answerId = (answer) ? in.readInt() : -1;
int cSize = in.readInt();
if (cSize < 0 || cSize > 0xFFFFFF) { // can't be more that 16 MiB
return null;
}
byte[] content = new byte[cSize];
in.readFully(content);
return answer ? new PPacketAnswer(name, packetId, answerId, content) : new PPacket(name, packetId, content);
}
/**
* Send the provided packet, without waiting for an answer.
*/
public void send(PPacket packet) throws IOException {
if (packet == null)
throw new IllegalArgumentException("packet can't be null");
if (packet.name == null)
throw new IllegalArgumentException("packet.name can't be null");
if (packet.content == null)
throw new IllegalArgumentException("packet.content can't be null");
byte[] nameBytes = packet.name.getBytes(ByteBuffer.NETWORK_CHARSET);
if (nameBytes.length > 127)
throw new IllegalArgumentException("packet.name must take fewer than 128 bytes when converted to UTF-8");
byte nameSize = (byte)nameBytes.length;
boolean answer = packet instanceof PPacketAnswer;
if (answer) nameSize *= -1;
synchronized (outSynchronizer) {
int packetId = nextSendId++;
packet.id = packetId;
out.write(new byte[] {nameSize});
out.write(nameBytes);
out.write(packetId);
if (answer)
out.write(((PPacketAnswer)packet).answer);
out.write(packet.content.length);
out.write(packet.content);
out.flush();
}
}
public void sendSilently(PPacket packet) {
try {
send(packet);
} catch (IOException ignored) {}
}
public void send(PPacket packet, PPacketListener<PPacketAnswer> answerCallback) throws IOException {
synchronized (answersCallbacks) {
/*
* This synch block ensure that the callback will be put in the listeners Map before
* we receve the answer (in case this is really really fast)
*/
send(packet);
answersCallbacks.put(packet.id, answerCallback);
}
}
public void addPacketListener(PPacketListener<PPacket> l) {
packetListeners.add(l);
}
public boolean removePacketListener(PPacketListener<PPacket> l) {
return packetListeners.remove(l);
}
public void addConnectionListener(PSocketConnectionListener l) {
connectionListeners.add(l);
}
public void removeConnectionListener(PSocketConnectionListener l) {
connectionListeners.remove(l);
}
@Override
public void close() {
try {
synchronized (outSynchronizer) {
if (isClosed.get()) return;
Log.info(getName() + " closing...");
connectionListeners.forEach(l -> {
try {
l.onDisconnect(this);
} catch (Exception e) {
Log.severe("Exception while calling PSocketConnectionListener.onDisconnect().", e);
}
});
socket.close();
isClosed.set(true);
}
} catch (IOException e) {
Log.warning(e);
}
}
public SocketAddress getRemoteAddress() {
return addr;
}
public boolean isClosed() {
return isClosed.get() || socket.isClosed();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("thread", getName())
.add("socket", socket.getRemoteSocketAddress())
.toString();
}
}

View File

@@ -1,17 +0,0 @@
package fr.pandacube.lib.core.net;
public interface PSocketConnectionListener {
/**
* Called when a socket is connected
* @param connection the connection
*/
void onConnect(PSocket connection);
/**
* Called just before a socket is disconnected
* @param connection the connection
*/
void onDisconnect(PSocket connection);
}

View File

@@ -1,30 +0,0 @@
package fr.pandacube.lib.core.network_api.client;
import java.io.PrintStream;
@Deprecated
public abstract class AbstractRequest {
private final String pass;
private final String command;
private String data;
protected AbstractRequest(String cmd, String p) {
if (cmd == null || cmd.isEmpty()) throw new IllegalArgumentException("Un message doit-être défini");
command = cmd;
pass = p;
}
protected void setData(String d) {
if (d == null) d = "";
data = d;
}
public void sendPacket(PrintStream out) {
out.print(pass + "\n");
out.print(command + "\n");
out.print(data.getBytes().length + "\n");
out.print(data);
out.flush();
}
}

View File

@@ -1,26 +0,0 @@
package fr.pandacube.lib.core.network_api.client;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.Socket;
@Deprecated
public class NetworkAPISender {
public static ResponseAnalyser sendRequest(InetSocketAddress cible, AbstractRequest request) throws IOException {
Socket s = new Socket(cible.getAddress(), cible.getPort());
PrintStream out = new PrintStream(s.getOutputStream());
request.sendPacket(out);
s.shutdownOutput();
ResponseAnalyser response = new ResponseAnalyser(s);
s.close();
return response;
}
}

View File

@@ -1,58 +0,0 @@
package fr.pandacube.lib.core.network_api.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
@Deprecated
public class ResponseAnalyser {
/**
* Indique si la requête s'est bien exécutée (l'entête de la réponse est
* 'ok')
*/
public final boolean good;
public final String data;
public ResponseAnalyser(Socket socket) throws IOException {
if (socket == null || socket.isClosed() || socket.isInputShutdown())
throw new IllegalArgumentException("le socket doit être non null et doit être ouvert sur le flux d'entrée");
// on lis la réponse
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
// lecture de la première ligne
line = in.readLine();
if (line == null)
throw new IOException("Not enough data to read first line of response.");
good = line.equalsIgnoreCase("OK");
// lecture de la deuxième ligne
line = in.readLine();
if (line == null)
throw new IOException("Not enough data to read second line of response.");
int data_size;
try {
data_size = Integer.parseInt(line);
} catch (NumberFormatException e) {
throw new RuntimeException("Réponse mal formée : la deuxième ligne doit-être un nombre entier");
}
// lecture du reste
StringBuilder sB_data = new StringBuilder();
char[] c = new char[100];
int nbC;
while ((nbC = in.read(c)) != -1)
sB_data.append(c, 0, nbC);
data = sB_data.toString();
if (data.getBytes().length != data_size) throw new RuntimeException("Réponse mal formée : " + data_size
+ " caractères annoncée dans la requête, mais " + data.getBytes().length + " s'y trouvent.");
}
}

View File

@@ -1,44 +0,0 @@
package fr.pandacube.lib.core.network_api.server;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
import fr.pandacube.lib.core.util.Log;
@Deprecated
public abstract class AbstractRequestExecutor {
public final String command;
public AbstractRequestExecutor(String cmd, NetworkAPIListener napiListener) {
command = cmd.toLowerCase();
napiListener.registerRequestExecutor(command, this);
}
public void execute(String data, Socket socket) throws IOException {
if (socket == null || socket.isClosed() || socket.isOutputShutdown())
throw new IllegalArgumentException("le socket doit être non null et doit être ouvert sur le flux d'entrée");
try {
Response rep = run(socket.getInetAddress(), data);
rep.sendPacket(new PrintStream(socket.getOutputStream()));
} catch (Exception e) {
new Response(false, e.toString()).sendPacket(new PrintStream(socket.getOutputStream()));
Log.severe(e);
}
}
/**
*
* @param data La représentation sous forme de String des données envoyés
* dans la requête
* @return La réponse à retourner au client
*/
protected abstract Response run(InetAddress source, String data);
}

View File

@@ -1,91 +0,0 @@
package fr.pandacube.lib.core.network_api.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.Arrays;
import java.util.HashMap;
import fr.pandacube.lib.core.util.Log;
@Deprecated
public class NetworkAPIListener implements Runnable {
private final int port;
final String pass;
private ServerSocket serverSocket;
private final HashMap<String, AbstractRequestExecutor> requestExecutors = new HashMap<>();
private final String name;
/**
* Instencie le côté serveur du NetworkAPI
*
* @param n nom du networkAPI (permet l'identification dans les logs)
* @param p le port d'écoute
* @param pa le mot de passe réseau
*/
public NetworkAPIListener(String n, int p, String pa) {
port = p;
pass = pa;
name = n;
}
@Override
public void run() {
synchronized (this) {
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
System.err.println(e.getMessage());
return;
}
}
Log.info("NetworkAPI '" + name + "' à l'écoute sur le port " + port);
try {
// réception des connexion client
while (!serverSocket.isClosed()) {
Thread t = new Thread(new PacketExecutor(serverSocket.accept(), this));
t.setDaemon(true);
t.start();
}
} catch (IOException ignored) {}
synchronized (this) {
try {
if (!serverSocket.isClosed())
serverSocket.close();
} catch (IOException ignored) {}
}
Log.info("NetworkAPI '" + name + "' ferme le port " + port);
}
/**
* Ferme le ServerSocket. Ceci provoque l'arrêt du thread associé à
* l'instance de la classe
*/
public synchronized void closeServerSocket() {
if (serverSocket != null) try {
serverSocket.close();
} catch (IOException ignored) {}
}
public int getPort() {
return port;
}
public void registerRequestExecutor(String command, AbstractRequestExecutor executor) {
requestExecutors.put(command, executor);
}
public AbstractRequestExecutor getRequestExecutor(String command) {
return requestExecutors.get(command);
}
public String getCommandList() {
return Arrays.toString(requestExecutors.keySet().toArray());
}
}

View File

@@ -1,65 +0,0 @@
package fr.pandacube.lib.core.network_api.server;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import fr.pandacube.lib.core.network_api.server.RequestAnalyser.BadRequestException;
import fr.pandacube.lib.core.util.Log;
/**
* Prends en charge un socket client et le transmet au gestionnaire de paquet
* correspondant.<br/>
* La connexion est fermée après chaque requête du client (règle pouvant
* évoluer)
*
* @author Marc Baloup
*
*/
@Deprecated
public class PacketExecutor implements Runnable {
private final Socket socket;
private final NetworkAPIListener networkAPIListener;
public PacketExecutor(Socket s, NetworkAPIListener napiListener) {
socket = s;
networkAPIListener = napiListener;
}
@Override
public void run() {
try {
// analyse de la requête
RequestAnalyser analyse = new RequestAnalyser(socket, networkAPIListener);
AbstractRequestExecutor executor = networkAPIListener.getRequestExecutor(analyse.command);
executor.execute(analyse.data, socket);
} catch (Throwable e) {
Response rep = new Response();
rep.good = false;
rep.data = e.toString();
try {
rep.sendPacket(new PrintStream(socket.getOutputStream()));
} catch (IOException ignored) {}
if (e instanceof IOException)
Log.warning("Unable to read packet from socket " + socket + ": " + e);
else if(e instanceof BadRequestException) {
if (e.getMessage().equals("wrong_password"))
Log.warning("Wrong password received from socket " + socket);
else if (e.getMessage().equals("command_not_exists"))
Log.severe("The command requested from the socket " + socket + " does not exist");
else
Log.severe(e);
}
else
Log.severe(e);
}
try {
socket.close();
} catch (Exception ignored) {}
}
}

View File

@@ -1,66 +0,0 @@
package fr.pandacube.lib.core.network_api.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
@Deprecated
public class RequestAnalyser {
public final String command;
public final String data;
public RequestAnalyser(Socket socket, NetworkAPIListener napiListener) throws IOException, BadRequestException {
if (socket == null || socket.isClosed() || socket.isInputShutdown() || napiListener == null)
throw new IllegalArgumentException(
"le socket doit être non null et doit être ouvert sur le flux d'entrée et napiListener ne doit pas être null");
// on lis la réponse
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
// lecture de la première ligne
line = in.readLine();
if (line == null || !line.equals(napiListener.pass)) throw new BadRequestException("wrong_password");
// lecture de la deuxième ligne
line = in.readLine();
if (line == null || napiListener.getRequestExecutor(line) == null)
throw new BadRequestException("command_not_exists");
command = line;
// lecture de la troisième ligne
line = in.readLine();
int data_size;
try {
data_size = Integer.parseInt(line);
} catch (NumberFormatException e) {
throw new BadRequestException("wrong_data_size_format");
}
// lecture du reste
StringBuilder sB_data = new StringBuilder();
char[] c = new char[100];
int nbC;
while ((nbC = in.read(c)) != -1)
sB_data.append(c, 0, nbC);
data = sB_data.toString();
if (data.getBytes().length != data_size) throw new BadRequestException("wrong_data_size");
socket.shutdownInput();
}
public static class BadRequestException extends Exception {
public BadRequestException(String message) {
super(message);
}
}
}

View File

@@ -1,30 +0,0 @@
package fr.pandacube.lib.core.network_api.server;
import java.io.PrintStream;
@Deprecated
public class Response {
public boolean good = true;
public String data = "";
public Response(boolean good, String data) {
this.good = good;
this.data = data;
}
/**
* Construit une réponse positive avec aucune donnée. Équivaut à
* <code>new Response(true, "")</code>
*/
public Response() {}
public void sendPacket(PrintStream out) {
if (data == null) data = "";
out.print((good ? "OK" : "ERROR") + "\n");
out.print(data.getBytes().length + "\n");
out.print(data);
out.flush();
}
}

View File

@@ -1,236 +0,0 @@
package fr.pandacube.lib.core.permissions;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalLong;
import java.util.Set;
import java.util.stream.LongStream;
import fr.pandacube.lib.core.chat.ChatUtil.DisplayTreeNode;
import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedEntity;
import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType;
import fr.pandacube.lib.core.util.Log;
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.
*/
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, true)));
}
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 Map<String, Boolean> listEffectivePermissions() {
return listEffectivePermissions(null, null);
}
public Map<String, Boolean> listEffectivePermissions(String server) {
return listEffectivePermissions(server, null);
}
public Map<String, Boolean> listEffectivePermissions(String server, String world) {
return Permissions.resolver.getEffectivePermissionList(name, type, server, world);
}
public LongStream getPermissionRangeValues(String permissionPrefix) {
return getPermissionRangeValues(permissionPrefix, null, null);
}
public LongStream getPermissionRangeValues(String permissionPrefix, String server) {
return getPermissionRangeValues(permissionPrefix, server, null);
}
public LongStream getPermissionRangeValues(String permissionPrefix, String server, String world) {
String prefixWithEndingDot = permissionPrefix.endsWith(".") ? permissionPrefix : (permissionPrefix + ".");
int prefixLength = prefixWithEndingDot.length();
return listEffectivePermissions(server, world).entrySet().stream()
.filter(Map.Entry::getValue) // permission must be positive
.map(Map.Entry::getKey) // keep only the permission node (key), since the value is always true
.filter(p -> p.startsWith(prefixWithEndingDot)) // keep only relevant permissions
.map(p -> p.substring(prefixLength)) // keep only what is after the prefix
.map(suffix -> { // convert to long
try {
return Long.parseLong(suffix);
}
catch (NumberFormatException e) {
return null;
}
})
.filter(Objects::nonNull)
.mapToLong(longSuffix -> longSuffix)
.sorted();
}
public OptionalLong getPermissionRangeMax(String permissionPrefix) {
return getPermissionRangeMax(permissionPrefix, null, null);
}
public OptionalLong getPermissionRangeMax(String permissionPrefix, String server) {
return getPermissionRangeMax(permissionPrefix, server, null);
}
public OptionalLong getPermissionRangeMax(String permissionPrefix, String server, String world) {
return getPermissionRangeValues(permissionPrefix, server, world).max();
}
public Boolean hasPermission(String permission) {
return hasPermission(permission, null, null);
}
public Boolean hasPermission(String permission, String server) {
return hasPermission(permission, server, null);
}
public Boolean hasPermission(String permission, String server, String world) {
Boolean ret = Permissions.resolver.getEffectivePermission(name, type, permission, server, world);
Log.debug("[Perm] For " + type.toString().toLowerCase() + " " + getName() + ", '" + permission + "' is " + ret);
return ret;
}
public boolean hasPermissionOr(String permission, String server, String world, boolean deflt) {
Boolean ret = hasPermission(permission, server, world);
return ret != null ? ret : deflt;
}
public boolean hasPermissionExpression(String permExpression, String server, String world) {
return PermissionExpressionParser.evaluate(permExpression, p -> hasPermissionOr(p, server, world, false));
}
public DisplayTreeNode debugPermission(String permission) {
return debugPermission(permission, null, null);
}
public DisplayTreeNode debugPermission(String permission, String server) {
return debugPermission(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) {
addSelfPermission(permission, null, null);
}
public void addSelfPermission(String permission, String server) {
addSelfPermission(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) {
removeSelfPermission(permission, null, null);
}
public void removeSelfPermission(String permission, String server) {
removeSelfPermission(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 getSelfPermissions(null, null);
}
public List<String> getSelfPermissions(String server) {
return getSelfPermissions(server, null);
}
public List<String> getSelfPermissions(String server, String world) {
return getBackendEntity().getSelfPermissions(server, world);
}
@Override
public boolean equals(Object obj) {
return obj instanceof PermEntity o
&& Objects.equals(name, o.name)
&& type == o.type;
}
@Override
public int hashCode() {
return Objects.hash(name, type);
}
}

View File

@@ -1,64 +0,0 @@
package fr.pandacube.lib.core.permissions;
import java.util.List;
import java.util.stream.Collectors;
import fr.pandacube.lib.core.permissions.PermissionsCachedBackendReader.CachedGroup;
import fr.pandacube.lib.core.permissions.SQLPermissions.EntityType;
public class PermGroup extends PermEntity {
/* package */ PermGroup(String name) {
super(name, EntityType.Group);
}
@Override
protected CachedGroup getBackendEntity() {
return Permissions.backendReader.getCachedGroup(name);
}
@Override
public String getName() {
return getInternalName();
}
@Override
public List<PermGroup> getInheritances() {
return fromCachedGroups(getBackendEntity().inheritances);
}
@Override
public List<String> getInheritancesString() {
return getBackendEntity().inheritances.stream()
.map(cg -> cg.name)
.collect(Collectors.toList());
}
public boolean isDefault() {
return getBackendEntity().deflt;
}
public void setDefault(boolean deflt) {
Permissions.backendWriter.setGroupDefault(name, deflt);
}
public void addInheritance(String group) {
Permissions.backendWriter.addInheritance(name, type, group);
}
public void addInheritance(PermGroup group) {
addInheritance(group.name);
}
public void removeInheritance(String group) {
Permissions.backendWriter.removeInheritance(name, type, group);
}
public void removeInheritance(PermGroup group) {
removeInheritance(group.name);
}
/* package */ static List<PermGroup> fromCachedGroups(List<CachedGroup> in) {
return in.stream()
.map(cg -> Permissions.getGroup(cg.name))
.collect(Collectors.toList());
}
}

View File

@@ -1,101 +0,0 @@
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()}.
*/
public List<PermGroup> getGroups() {
return getInheritances();
}
/**
* Alias for {@link #getInheritances()}.
*/
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
*/
public boolean isInGroup(String group) {
return inheritsFromGroup(group, false);
}
public boolean isUsingDefaultGroups() {
return getBackendEntity().usingDefaultGroups;
}
public void setGroup(String group) {
Permissions.backendWriter.setInheritance(name, type, group);
}
public void setGroup(PermGroup group) {
setGroup(group.name);
}
public void addGroup(String group) {
Permissions.backendWriter.addInheritance(name, type, group);
}
public void addGroup(PermGroup group) {
addGroup(group.name);
}
public void removeGroup(String group) {
Permissions.backendWriter.removeInheritance(name, type, group);
}
public void removeGroup(PermGroup group) {
removeGroup(group.name);
}
}

View File

@@ -1,129 +0,0 @@
package fr.pandacube.lib.core.permissions;
import java.util.Iterator;
import java.util.function.Function;
import com.fathzer.soft.javaluator.AbstractEvaluator;
import com.fathzer.soft.javaluator.BracketPair;
import com.fathzer.soft.javaluator.Constant;
import com.fathzer.soft.javaluator.Operator;
import com.fathzer.soft.javaluator.Operator.Associativity;
import com.fathzer.soft.javaluator.Parameters;
public class PermissionExpressionParser {
private static final PermissionEvaluator PERMISSION_EVALUATOR = new PermissionEvaluator();
public static boolean evaluate(String permString, LitteralPermissionTester permTester) {
try {
return PERMISSION_EVALUATOR.evaluate(permString, permTester);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Cant evaluate the provided permission expression: '" + permString + "'", e);
}
}
public interface LitteralPermissionTester extends Function<String, Boolean> { }
private static class PermissionEvaluator extends AbstractEvaluator<Boolean> {
private static final Operator NOT = new Operator("!", 1, Associativity.LEFT, 3);
private static final Operator AND = new Operator("&&", 2, Associativity.LEFT, 2);
private static final Operator OR = new Operator("||", 2, Associativity.LEFT, 1);
private static final Constant TRUE = new Constant("true");
private static final Constant FALSE = new Constant("false");
private static final Parameters PARAMETERS;
static {
PARAMETERS = new Parameters();
PARAMETERS.add(NOT);
PARAMETERS.add(AND);
PARAMETERS.add(OR);
PARAMETERS.add(TRUE);
PARAMETERS.add(FALSE);
PARAMETERS.addExpressionBracket(BracketPair.PARENTHESES);
}
public PermissionEvaluator() {
super(PARAMETERS);
}
@Override
protected Boolean toValue(String literal, Object evaluationContext) {
if (literal.contains(" ") || literal.contains("|") || literal.contains("&"))
throw new IllegalArgumentException("Unable to parse the following part of permission expression as one permission node: '" + literal + "'");
return evaluationContext instanceof LitteralPermissionTester pt ? pt.apply(literal) : false;
}
@Override
protected Boolean evaluate(Operator operator, Iterator<Boolean> operands, Object evaluationContext) {
if (operator == NOT) {
return !operands.next();
} else if (operator == OR) {
Boolean o1 = operands.next();
Boolean o2 = operands.next();
return o1 || o2;
} else if (operator == AND) {
Boolean o1 = operands.next();
Boolean o2 = operands.next();
return o1 && o2;
} else {
return super.evaluate(operator, operands, evaluationContext);
}
}
@Override
protected Boolean evaluate(Constant constant, Object evaluationContext) {
if (constant == TRUE)
return true;
if (constant == FALSE)
return false;
return super.evaluate(constant, evaluationContext);
}
}
/*
public static void main(String[] args) {
java.util.List<String> pList = java.util.Arrays.asList("p1.cmd", "p1.toto", "p2.lol");
LitteralPermissionTester tester = p -> pList.contains(p);
for (String permExpr : java.util.Arrays.asList(
"p1.cmd", // true
"p1.notexist", // false
"p2lol.lol", // false
"!p1.notexist", // true
"!p1.cmd", // false
"p1.cmd!", // false
"p1.cmd! p2.lol", // exception
"p1.cmd || p1.toto", // true || true == true
"p1.cmd || p1.notexist", // true || false == true
"p1.fefef || p2.lol", // false || true == true
"p1.fefef || p2.lolilol", // false || false == false
"p1.cmd && p1.toto", // true && true == true
"p1.cmd && p1.notexist", // true && false == false
"p1.fefef && p2.lol", // false && true == false
"p1.fefef && p2.lolilol", // false && false == false
"p1.cmd && !p1.toto ", // true && !true == false
" !p1.cmd && p1.toto", // !true && true == false
"!p1.cmd & p1.toto", // exception
"!p1.cmd | p1.toto", // exception
"p1.not exist" // exception
)) {
try {
System.out.println(permExpr + " -> " + evaluate(permExpr, tester));
} catch (Exception e) {
e.printStackTrace();
}
}
}
*/
}

View File

@@ -1,104 +0,0 @@
package fr.pandacube.lib.core.permissions;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import fr.pandacube.lib.core.db.DB;
import fr.pandacube.lib.core.db.DBConnection;
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)}.
*/
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;
}
}
public static void addSpecialPermissions(SpecialPermission... specialPermissions) {
checkInitialized();
if (specialPermissions == null)
return;
resolver.specialPermissions.addAll(Arrays.asList(specialPermissions));
}
private static void checkInitialized() {
if (backendReader == null) {
throw new IllegalStateException("Permissions system not initialized. Check the server logs to check if there is an error during the startup, and check if the init() method is called properly.");
}
}
public static void clearPlayerCache(UUID playerId) {
checkInitialized();
backendReader.clearPlayerCache(playerId);
resolver.clearPlayerFromCache(playerId);
}
public static void clearCache(Runnable then) {
checkInitialized();
backendReader.clearAndResetCacheAsync(() -> {
resolver.clearCache();
if (then != null)
then.run();
});
}
public static PermPlayer getPlayer(UUID playerId) {
checkInitialized();
return new PermPlayer(playerId);
}
public static void precachePlayerAsync(UUID playerId) {
checkInitialized();
Thread t = new Thread(() -> {
try {
backendReader.getCachedPlayer(playerId);
} catch (RuntimeException e) {
Log.warning("Cant init player cache asynchronously: " + e.getMessage());
}
}, "Async permissions player cache loader");
t.setDaemon(true);
t.start();
}
public static PermGroup getGroup(String name) {
checkInitialized();
return new PermGroup(name);
}
public static List<PermGroup> getGroups() {
checkInitialized();
return PermGroup.fromCachedGroups(backendReader.getGroups());
}
public static List<PermGroup> getDefaultGroups() {
checkInitialized();
return PermGroup.fromCachedGroups(backendReader.getDefaultGroups());
}
public static List<String> getFullPermissionsList() {
return backendReader.getFullPermissionsList();
}
}

View File

@@ -1,289 +0,0 @@
package fr.pandacube.lib.core.permissions;
import java.util.Objects;
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) {
Objects.requireNonNull(name, "name cannot be null");
Objects.requireNonNull(type, "type cannot be null");
Objects.requireNonNull(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) {
Objects.requireNonNull(name, "name cannot be null");
Objects.requireNonNull(type, "type cannot be null");
Objects.requireNonNull(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) {
Objects.requireNonNull(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) {
if (deflt) {
// update just in case
if ("true".equals(entry.get(SQLPermissions.value)))
return;
entry.set(SQLPermissions.value, "true");
entry.save();
}
else {
// delete
entry.delete();
}
}
else if (deflt) {
// 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) {
Objects.requireNonNull(name, "name cannot be null");
Objects.requireNonNull(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) {
if (prefix != null) {
// update
entry.set(SQLPermissions.value, prefix);
entry.save();
}
else {
// delete
entry.delete();
}
}
else if (prefix != null) {
// insert
addEntry(name, type, "prefix", prefix, null, null);
}
} catch (DBException e) {
throw new RuntimeException(e);
}
}
/* package */ void setSelfSuffix(String name, EntityType type, String suffix) {
Objects.requireNonNull(name, "name cannot be null");
Objects.requireNonNull(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) {
if (suffix != null) {
// update
entry.set(SQLPermissions.value, suffix);
entry.save();
}
else {
// delete
entry.delete();
}
}
else if (suffix != null) {
// insert
addEntry(name, type, "suffix", suffix, null, null);
}
} catch (DBException e) {
throw new RuntimeException(e);
}
}
/* package */ void addInheritance(String name, EntityType type, String inheritance) {
Objects.requireNonNull(name, "name cannot be null");
Objects.requireNonNull(type, "type cannot be null");
Objects.requireNonNull(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) {
throw new RuntimeException(e);
}
}
/* package */ void removeInheritance(String name, EntityType type, String inheritance) {
Objects.requireNonNull(name, "name cannot be null");
Objects.requireNonNull(type, "type cannot be null");
Objects.requireNonNull(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) {
Objects.requireNonNull(name, "name cannot be null");
Objects.requireNonNull(type, "type cannot be null");
Objects.requireNonNull(inheritance, "inheritance cannot be null");
name = name.toLowerCase();
inheritance = inheritance.toLowerCase();
String key = type == EntityType.Group ? "inheritances" : "groups";
try {
DB.delete(SQLPermissions.class,
SQLPermissions.name.like(name)
.and(SQLPermissions.type.eq(type.getCode()))
.and(SQLPermissions.key.like(key))
);
addEntry(name, type, key, inheritance, null, null);
} catch (DBException e) {
throw new RuntimeException(e);
}
}
private boolean deleteEntry(String name, EntityType type,
String key, String value,
String server, String world) {
try {
return DB.delete(SQLPermissions.class,
SQLPermissions.name.like(name)
.and(SQLPermissions.type.eq(type.getCode()))
.and(SQLPermissions.key.like(key))
.and(SQLPermissions.value.like(value))
.and(server == null ? SQLPermissions.server.isNull() : SQLPermissions.server.like(server))
.and(world == null ? SQLPermissions.world.isNull() : SQLPermissions.world.like(world))
) >= 1;
} catch (DBException e) {
throw new RuntimeException(e);
}
}
private boolean hasEntry(String name, EntityType type,
String key, String value,
String server, String world) {
return getEntry(name, type, key, value, server, world) != null;
}
private SQLPermissions getEntry(String name, EntityType type,
String key, String value,
String server, String world) {
try {
return DB.getFirst(SQLPermissions.class,
SQLPermissions.name.like(name)
.and(SQLPermissions.type.eq(type.getCode()))
.and(SQLPermissions.key.like(key))
.and(SQLPermissions.value.like(value))
.and(server == null ? SQLPermissions.server.isNull() : SQLPermissions.server.like(server))
.and(world == null ? SQLPermissions.world.isNull() : SQLPermissions.world.like(world))
);
} catch (DBException e) {
throw new RuntimeException(e);
}
}
private void addEntry(String name, EntityType type,
String key, String value,
String server, String world) {
SQLPermissions entry = new SQLPermissions()
.set(SQLPermissions.name, name)
.set(SQLPermissions.type, type.getCode())
.set(SQLPermissions.key, key)
.set(SQLPermissions.value, value)
.set(SQLPermissions.server, server)
.set(SQLPermissions.world, world);
try {
entry.save();
} catch (DBException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1,304 +0,0 @@
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 final Cache<UUID, CachedPlayer> usersCache = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
private Set<String> fullPermissionsList = new TreeSet<>();
/* 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).toLowerCase());
})
.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;
player.groups.addAll(getDefaultGroups());
}
return player;
}
private final 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));
String groupSelfPrefix = null;
if (groupRawData.containsKey("prefix")) {
groupSelfPrefix = groupRawData.get("prefix").stream()
.map(e -> e.get(SQLPermissions.value))
.collect(Collectors.joining());
}
String groupSelfSuffix = null;
if (groupRawData.containsKey("suffix")) {
groupSelfSuffix = groupRawData.get("suffix").stream()
.map(e -> e.get(SQLPermissions.value))
.collect(Collectors.joining());
}
Map<ServerWorldKey, List<String>> groupSelfPerms = new LinkedHashMap<>();
if (groupRawData.containsKey("permissions")) {
groupSelfPerms = groupRawData.get("permissions").stream()
.peek(e -> {
String value = e.get(SQLPermissions.value);
newFullPermissionsList.add(value.substring(value.startsWith("-") ? 1 : 0));
})
.collect(Collectors.groupingBy(e -> new ServerWorldKey(e.get(SQLPermissions.server), e.get(SQLPermissions.world)),
LinkedHashMap::new,
Collectors.mapping(e -> e.get(SQLPermissions.value),
Collectors.toList()
)
)
);
}
CachedGroup group = new CachedGroup(groupName, groupSelfPrefix, groupSelfSuffix, groupDefault, groupSelfPerms);
newData.put(groupName, group);
if (groupRawData.containsKey("inheritances")) {
groupRawData.get("inheritances").stream()
.map(e -> e.get(SQLPermissions.value))
.forEach(g -> {
initGroup(g, groupsRawData, newData, newFullPermissionsList);
group.inheritances.add(newData.get(g));
});
}
}
/* package */ static abstract class CachedEntity {
public final String name;
private final String selfPrefix, selfSuffix;
private final Map<ServerWorldKey, List<String>> selfPermissions;
private CachedEntity(String n, String p, String s,
Map<ServerWorldKey, List<String>> perms) {
name = n; selfPrefix = p; selfSuffix = s; selfPermissions = perms;
}
/* package */ List<String> getSelfPermissions(String server, String world) {
return selfPermissions.getOrDefault(new ServerWorldKey(server, world), new ArrayList<>());
}
/* package */ Set<ServerWorldKey> getSelfPermissionsServerWorldKeys() {
return new TreeSet<>(selfPermissions.keySet());
}
/* package */ String getSelfPrefix() {
return selfPrefix;
}
/* package */ String getSelfSuffix() {
return selfSuffix;
}
}
/* package */ static class CachedPlayer extends CachedEntity {
public final UUID playerId;
public final List<CachedGroup> groups = new ArrayList<>();
public boolean usingDefaultGroups = false;
private CachedPlayer(UUID pl, String p, String s,
Map<ServerWorldKey, List<String>> perms) {
super(pl.toString(), p, s, perms);
playerId = pl;
}
}
/* package */ static class CachedGroup extends CachedEntity {
public final boolean deflt;
public final List<CachedGroup> inheritances = new ArrayList<>();
private CachedGroup(String n, String p, String s,
boolean dflt, Map<ServerWorldKey, List<String>> perms) {
super(n, p, s, perms);
deflt = dflt;
}
}
}

View File

@@ -1,565 +0,0 @@
package fr.pandacube.lib.core.permissions;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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;
public class PermissionsResolver {
private final 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 (effectivePermissionsListCache) {
effectivePermissionsListCache.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();
effectivePermissionsListCache.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 final Cache<DataCacheKey, String> effectiveDataCache = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
private String getEffectiveData(String name, EntityType type, DataType dataType) {
Objects.requireNonNull(name, "name cant be null");
Objects.requireNonNull(type, "type cant be null");
try {
return effectiveDataCache.get(new DataCacheKey(name, type, dataType), () -> 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 + ":\n"
+ ChatUtil.treeView(resolutionResult.toDisplayTreeNode(), true).stream()
.map(Chat::getLegacyText)
.collect(Collectors.joining(ChatColor.RESET + "\n")));
}
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(Objects::nonNull)
.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);
if (result == null && !conflict && !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()));
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) {
return obj instanceof DataCacheKey o
&& 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;
DataType(CachedEntityGetter<String> g) {
getter = g;
}
}
private interface CachedEntityGetter<R> {
R apply(CachedEntity a);
}
private final Cache<PermCacheKey, Map<String, Boolean>> effectivePermissionsListCache = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
/* package */ Map<String, Boolean> getEffectivePermissionList(String name, EntityType type, String server, String world) {
Objects.requireNonNull(name, "name cant be null");
Objects.requireNonNull(type, "type cant be null");
Preconditions.checkArgument(world == null || server != null, "world not null but server is null");
String fServer = server == null ? null : server.toLowerCase();
String fWorld = world == null ? null : world.toLowerCase();
try {
return effectivePermissionsListCache.get(new PermCacheKey(name, type, null, fServer, fWorld), () -> {
Map<String, Boolean> permList = new LinkedHashMap<>();
for (String perm : backendReader.getFullPermissionsList()) {
Boolean has = getEffectivePermission(name, type, perm, fServer, fWorld);
if (has == null)
continue;
permList.put(perm.toLowerCase(), has);
}
return permList;
});
} catch (ExecutionException e) {
Log.severe(e);
return null;
}
}
private final Cache<PermCacheKey, PermState> effectivePermissionsCache = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
/* package */ Boolean getEffectivePermission(String name, EntityType type, String permission, String server, String world) {
Objects.requireNonNull(name, "name cant be null");
Objects.requireNonNull(type, "type cant be null");
Objects.requireNonNull(permission, "permission cant be null");
Preconditions.checkArgument(world == null || server != null, "world not null but server is null");
boolean reversed = false;
if (permission.startsWith("-")) {
permission = permission.substring(1);
reversed = true;
}
String fPermission = permission.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),
() -> resolvePermission(name, type, fPermission, fServer, fWorld)
).value;
return resolved == null ? null : (reversed != resolved);
} 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 + ":\n"
+ ChatUtil.treeView(resolutionResult.toDisplayTreeNode(), true).stream()
.map(Chat::getLegacyText)
.collect(Collectors.joining(ChatColor.RESET + "\n")));
}
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) {
resolutionNode.conflictMessage = (resolutionNode.conflictMessage == null ? "" : (resolutionNode.conflictMessage + " ; "))
+ "Unsolvable conflict between inheritances";
resolutionNode.conflict = true;
}
return resolutionNode;
}
/* package */ final List<SpecialPermission> specialPermissions = new ArrayList<>();
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 = Permissions.getPlayer(((CachedPlayer) entity).playerId);
ParsedSelfPermission specialPerm = null;
for (SpecialPermission spePerm : specialPermissions) {
if (spePerm.match().match(permission)) {
boolean res = spePerm.tester().test(permP, permission, server, world);
specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL);
break;
}
}
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(Objects::nonNull)
.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) {
conflict = "Unsolvable conflit between explicit permissions";
}
else if (wildcardGranted != wildcardRevoqued) {
result = PermState.of(wildcardGranted);
}
else if (wildcardGranted) {
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);
selfPermissions.forEach(p -> node.children.add(p.toDisplayTreeNode()));
if (result == PermState.UNDEFINED && !conflict && !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()));
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)));
}
}
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) {
return obj instanceof PermCacheKey o
&& 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;
PermState(Boolean v) { value = v; }
private static PermState of(Boolean v) {
return v == null ? UNDEFINED : v ? GRANTED : REVOQUED;
}
}
}

View File

@@ -1,46 +0,0 @@
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 "permissions";
}
public static final SQLField<SQLPermissions, String> name = field(VARCHAR(64), false);
public static final SQLField<SQLPermissions, Integer> type = field(TINYINT, false);
public static final SQLField<SQLPermissions, String> key = field(VARCHAR(256), false);
public static final SQLField<SQLPermissions, String> value = field(VARCHAR(256), false);
public static final SQLField<SQLPermissions, String> server = field(VARCHAR(64), true);
public static final SQLField<SQLPermissions, String> world = field(VARCHAR(64), true);
public enum EntityType {
User,
Group;
public int getCode() {
return ordinal();
}
public static EntityType getByCode(int code) {
if (code >= 0 && code < values().length)
return values()[code];
return null;
}
}
}

View File

@@ -1,29 +0,0 @@
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) {
return obj instanceof ServerWorldKey o
&& Objects.equals(server, o.server)
&& Objects.equals(world, o.world);
}
@Override
public int hashCode() {
return Objects.hash(world, server);
}
private static final Comparator<String> STR_NULL_FIRST_COMPARATOR = Comparator.nullsFirst(String::compareToIgnoreCase);
@Override
public int compareTo(ServerWorldKey o) {
return Comparator.comparing((ServerWorldKey k) -> k.server, STR_NULL_FIRST_COMPARATOR)
.thenComparing(k -> k.world, STR_NULL_FIRST_COMPARATOR)
.compare(this, o);
}
}

View File

@@ -1,16 +0,0 @@
package fr.pandacube.lib.core.permissions;
/**
* Represents a permission node that is based on an arbitrary player state.
*/
public record SpecialPermission(PermissionMatcher match, PermissionTester tester) {
public interface PermissionMatcher {
boolean match(String permission);
}
public interface PermissionTester {
boolean test(PermPlayer player, String permission, String server, String world);
}
}

View File

@@ -1,21 +1,21 @@
package fr.pandacube.lib.core.players;
import static fr.pandacube.lib.core.chat.ChatStatic.dataText;
import static fr.pandacube.lib.core.chat.ChatStatic.successText;
import static fr.pandacube.lib.core.chat.ChatStatic.text;
import static fr.pandacube.lib.core.chat.ChatStatic.warningText;
import java.util.Calendar;
import java.util.OptionalLong;
import java.util.UUID;
import java.util.stream.LongStream;
import fr.pandacube.lib.core.chat.Chat;
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;
import fr.pandacube.lib.chat.Chat;
import fr.pandacube.lib.chat.ChatColorUtil;
import fr.pandacube.lib.db.DBException;
import fr.pandacube.lib.permissions.PermPlayer;
import fr.pandacube.lib.permissions.Permissions;
import fr.pandacube.lib.util.Log;
import static fr.pandacube.lib.chat.ChatStatic.dataText;
import static fr.pandacube.lib.chat.ChatStatic.successText;
import static fr.pandacube.lib.chat.ChatStatic.text;
import static fr.pandacube.lib.chat.ChatStatic.warningText;
public interface IOffPlayer {

View File

@@ -8,8 +8,8 @@ import java.util.stream.LongStream;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import fr.pandacube.lib.core.chat.Chat;
import fr.pandacube.lib.core.db.DBException;
import fr.pandacube.lib.chat.Chat;
import fr.pandacube.lib.db.DBException;
import net.kyori.adventure.identity.Identified;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;

View File

@@ -13,15 +13,15 @@ import java.util.stream.Collectors;
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.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.md_5.bungee.api.chat.BaseComponent;
import fr.pandacube.lib.chat.Chat;
import fr.pandacube.lib.db.DB;
import fr.pandacube.lib.db.DBInitTableException;
import fr.pandacube.lib.util.Log;
public abstract class IPlayerManager<OP extends IOnlinePlayer, OF extends IOffPlayer> {
private static IPlayerManager<?, ?> instance;

View File

@@ -19,11 +19,11 @@ 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;
import fr.pandacube.lib.db.DB;
import fr.pandacube.lib.db.DBException;
import fr.pandacube.lib.db.SQLOrderBy;
import fr.pandacube.lib.util.LevenshteinDistance;
import fr.pandacube.lib.util.Log;
/*
* Etape de recherche de joueur :

View File

@@ -6,12 +6,12 @@ import java.util.GregorianCalendar;
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;
import fr.pandacube.lib.core.util.Log;
import fr.pandacube.lib.db.DB;
import fr.pandacube.lib.db.DBException;
import fr.pandacube.lib.db.SQLElement;
import fr.pandacube.lib.db.SQLElementList;
import fr.pandacube.lib.db.SQLField;
import fr.pandacube.lib.util.Log;
public class SQLPlayer extends SQLElement<SQLPlayer> {

View File

@@ -2,12 +2,12 @@ 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.SQLElementList;
import fr.pandacube.lib.core.db.SQLFKField;
import fr.pandacube.lib.core.db.SQLField;
import fr.pandacube.lib.db.DB;
import fr.pandacube.lib.db.DBException;
import fr.pandacube.lib.db.SQLElement;
import fr.pandacube.lib.db.SQLElementList;
import fr.pandacube.lib.db.SQLFKField;
import fr.pandacube.lib.db.SQLField;
public class SQLPlayerConfig extends SQLElement<SQLPlayerConfig> {

View File

@@ -3,10 +3,10 @@ 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;
import fr.pandacube.lib.db.DB;
import fr.pandacube.lib.db.DBException;
import fr.pandacube.lib.db.SQLElement;
import fr.pandacube.lib.db.SQLFKField;
public class SQLPlayerIgnore extends SQLElement<SQLPlayerIgnore> {

View File

@@ -2,12 +2,12 @@ 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;
import fr.pandacube.lib.db.DB;
import fr.pandacube.lib.db.DBException;
import fr.pandacube.lib.db.SQLElement;
import fr.pandacube.lib.db.SQLFKField;
import fr.pandacube.lib.db.SQLField;
import fr.pandacube.lib.util.Log;
public class SQLPlayerNameHistory extends SQLElement<SQLPlayerNameHistory> {

View File

@@ -2,9 +2,9 @@ package fr.pandacube.lib.core.players;
import java.util.UUID;
import fr.pandacube.lib.core.chat.Chat;
import fr.pandacube.lib.core.db.DBInitTableException;
import fr.pandacube.lib.core.util.Log;
import fr.pandacube.lib.chat.Chat;
import fr.pandacube.lib.db.DBInitTableException;
import fr.pandacube.lib.util.Log;
import net.kyori.adventure.text.Component;
/**

View File

@@ -2,7 +2,7 @@ package fr.pandacube.lib.core.search;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import fr.pandacube.lib.core.util.Log;
import fr.pandacube.lib.util.Log;
import java.util.ArrayList;
import java.util.HashMap;

View File

@@ -1,57 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class AmountPerTimeLimiter {
private final double maxAmount;
private final long duration;
private List<Entry> entries = new ArrayList<>();
public AmountPerTimeLimiter(double a, long d) {
maxAmount = a;
duration = d;
}
private static class Entry {
private final long time;
private double amount;
public Entry(long t, double a) {
time = t; amount = a;
}
}
public synchronized double getAmountSinceDuration() {
long currT = System.currentTimeMillis();
entries = entries.stream()
.filter(e -> e.time > currT - duration)
.collect(Collectors.toList());
return entries.stream()
.mapToDouble(e -> e.amount)
.sum();
}
public synchronized void add(double amount) {
long currT = System.currentTimeMillis();
if (!entries.isEmpty() && entries.get(entries.size()-1).time > currT - 1000)
entries.get(entries.size()-1).amount += amount;
else
entries.add(new Entry(System.currentTimeMillis(), amount));
}
public boolean canAdd(double amount) {
return getAmountSinceDuration() + amount < maxAmount;
}
public double getRemainingForNow() {
return Math.max(0, maxAmount - getAmountSinceDuration());
}
}

View File

@@ -1,125 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class BiMap<K, V> implements Iterable<Entry<K, V>> {
protected final Map<K, V> map;
protected final Map<V, K> inversedMap;
private BiMap<V, K> reversedView = null;
@SuppressWarnings("unchecked")
public BiMap(Supplier<Map<?, ?>> mapSupplier) {
map = (Map<K, V>) mapSupplier.get();
inversedMap = (Map<V, K>) mapSupplier.get();
}
public BiMap() {
this(HashMap::new);
}
public BiMap(Map<K, V> source) {
this();
putAll(source);
}
/*
* Only used for #reversedView()
*/
private BiMap(BiMap<V, K> rev) {
map = rev.inversedMap;
inversedMap = rev.map;
reversedView = rev;
}
public synchronized void put(K k, V v) {
if (containsKey(k))
remove(k);
if (containsValue(v))
removeValue(v);
map.put(k, v);
inversedMap.put(v, k);
}
public synchronized void putAll(Map<? extends K, ? extends V> source) {
for (Map.Entry<? extends K, ? extends V> e : source.entrySet()) {
put(e.getKey(), e.getValue());
}
}
public synchronized V get(K k) {
return map.get(k);
}
public synchronized K getKey(V v) {
return inversedMap.get(v);
}
public synchronized boolean containsKey(K k) {
return map.containsKey(k);
}
public synchronized boolean containsValue(V v) {
return inversedMap.containsKey(v);
}
public synchronized V remove(K k) {
V v = map.remove(k);
inversedMap.remove(v);
return v;
}
public synchronized K removeValue(V v) {
K k = inversedMap.remove(v);
map.remove(k);
return k;
}
@Override
public Iterator<Entry<K, V>> iterator() {
return Collections.unmodifiableSet(map.entrySet()).iterator();
}
public Set<K> keySet() {
return Collections.unmodifiableSet(map.keySet());
}
public Set<V> valuesSet() {
return Collections.unmodifiableSet(inversedMap.keySet());
}
public Map<K, V> asMap() {
return Collections.unmodifiableMap(map);
}
public BiMap<V, K> reversedView() {
if (reversedView == null)
reversedView = new BiMap<>(this);
return reversedView;
}
public synchronized void forEach(BiConsumer<K, V> c) {
for(Entry<K, V> entry : this) {
c.accept(entry.getKey(), entry.getValue());
}
}
public int size() {
return map.size();
}
public synchronized void clear() {
map.clear();
inversedMap.clear();
}
}

View File

@@ -1,642 +0,0 @@
package fr.pandacube.lib.core.util;
// from https://github.com/frode-carlsen/cron/blob/master/java8/src/main/java/fc/cron/CronExpression.java
// if there are changes, indicate them here (there is none currently)
/*
* Copyright (C) 2012- Frode Carlsen.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Note: rewritten to standard Java 8 DateTime by zemiak (c) 2016
*/
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This provides cron support for java8 using java-time.
* <P>
*
* Parser for unix-like cron expressions: Cron expressions allow specifying combinations of criteria for time
* such as: &quot;Each Monday-Friday at 08:00&quot; or &quot;Every last friday of the month at 01:30&quot;
* <p>
* A cron expressions consists of 5 or 6 mandatory fields (seconds may be omitted) separated by space. <br>
* These are:
*
* <table cellspacing="8">
* <tr>
* <th align="left">Field</th>
* <th align="left">&nbsp;</th>
* <th align="left">Allowable values</th>
* <th align="left">&nbsp;</th>
* <th align="left">Special Characters</th>
* </tr>
* <tr>
* <td align="left"><code>Seconds (may be omitted)</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>0-59</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Minutes</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>0-59</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Hours</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>0-23</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Day of month</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>1-31</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * ? / L W</code></td>
* </tr>
* <tr>
* <td align="left"><code>Month</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>1-12 or JAN-DEC (note: english abbreviations)</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Day of week</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>1-7 or MON-SUN (note: english abbreviations)</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * ? / L #</code></td>
* </tr>
* </table>
*
* <P>
* '*' Can be used in all fields and means 'for all values'. E.g. &quot;*&quot; in minutes, means 'for all minutes'
* <P>
* '?' Can be used in Day-of-month and Day-of-week fields. Used to signify 'no special value'. It is used when one want
* to specify something for one of those two fields, but not the other.
* <P>
* '-' Used to specify a time interval. E.g. &quot;10-12&quot; in Hours field means 'for hours 10, 11 and 12'
* <P>
* ',' Used to specify multiple values for a field. E.g. &quot;MON,WED,FRI&quot; in Day-of-week field means &quot;for
* monday, wednesday and friday&quot;
* <P>
* '/' Used to specify increments. E.g. &quot;0/15&quot; in Seconds field means &quot;for seconds 0, 15, 30, ad
* 45&quot;. And &quot;5/15&quot; in seconds field means &quot;for seconds 5, 20, 35, and 50&quot;. If '*' s specified
* before '/' it is the same as saying it starts at 0. For every field there's a list of values that can be turned on or
* off. For Seconds and Minutes these range from 0-59. For Hours from 0 to 23, For Day-of-month it's 1 to 31, For Months
* 1 to 12. &quot;/&quot; character helsp turn some of these values back on. Thus &quot;7/6&quot; in Months field
* specify just Month 7. It doesn't turn on every 6 month following, since cron fields never roll over
* <P>
* 'L' Can be used on Day-of-month and Day-of-week fields. It signifies last day of the set of allowed values. In
* Day-of-month field it's the last day of the month (e.g.. 31 jan, 28 feb (29 in leap years), 31 march, etc.). In
* Day-of-week field it's Sunday. If there's a prefix, this will be subtracted (5L in Day-of-month means 5 days before
* last day of Month: 26 jan, 23 feb, etc.)
* <P>
* 'W' Can be specified in Day-of-Month field. It specifies closest weekday (monday-friday). Holidays are not accounted
* for. &quot;15W&quot; in Day-of-Month field means 'closest weekday to 15 i in given month'. If the 15th is a Saturday,
* it gives Friday. If 15th is a Sunday, the it gives following Monday.
* <P>
* '#' Can be used in Day-of-Week field. For example: &quot;5#3&quot; means 'third friday in month' (day 5 = friday, #3
* - the third). If the day does not exist (e.g. &quot;5#5&quot; - 5th friday of month) and there aren't 5 fridays in
* the month, then it won't match until the next month with 5 fridays.
* <P>
* <b>Case-sensitive</b> No fields are case-sensitive
* <P>
* <b>Dependencies between fields</b> Fields are always evaluated independently, but the expression doesn't match until
* the constraints of each field are met. Overlap of intervals are not allowed. That is: for
* Day-of-week field &quot;FRI-MON&quot; is invalid,but &quot;FRI-SUN,MON&quot; is valid
*
*/
public class CronExpression {
enum CronFieldType {
SECOND(0, 59, null) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getSecond();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
return dateTime.withSecond(value).withNano(0);
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
return dateTime.plusMinutes(1).withSecond(0).withNano(0);
}
},
MINUTE(0, 59, null) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getMinute();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
return dateTime.withMinute(value).withSecond(0).withNano(0);
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
return dateTime.plusHours(1).withMinute(0).withSecond(0).withNano(0);
}
},
HOUR(0, 23, null) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getHour();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
return dateTime.withHour(value).withMinute(0).withSecond(0).withNano(0);
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
return dateTime.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
}
},
DAY_OF_MONTH(1, 31, null) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getDayOfMonth();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
return dateTime.withDayOfMonth(value).withHour(0).withMinute(0).withSecond(0).withNano(0);
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
return dateTime.plusMonths(1).withDayOfMonth(0).withHour(0).withMinute(0).withSecond(0).withNano(0);
}
},
MONTH(1, 12,
Arrays.asList("JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC")) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getMonthValue();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
return dateTime.withMonth(value).withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
return dateTime.plusYears(1).withMonth(1).withHour(0).withDayOfMonth(1).withMinute(0).withSecond(0).withNano(0);
}
},
DAY_OF_WEEK(1, 7, Arrays.asList("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN")) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getDayOfWeek().getValue();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
throw new UnsupportedOperationException();
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
throw new UnsupportedOperationException();
}
};
final int from, to;
final List<String> names;
CronFieldType(int from, int to, List<String> names) {
this.from = from;
this.to = to;
this.names = names;
}
/**
* @param dateTime {@link ZonedDateTime} instance
* @return The field time or date value from {@code dateTime}
*/
abstract int getValue(ZonedDateTime dateTime);
/**
* @param dateTime Initial {@link ZonedDateTime} instance to use
* @param value to set for this field in {@code dateTime}
* @return {@link ZonedDateTime} with {@code value} set for this field and all smaller fields cleared
*/
abstract ZonedDateTime setValue(ZonedDateTime dateTime, int value);
/**
* Handle when this field overflows and the next higher field should be incremented
*
* @param dateTime Initial {@link ZonedDateTime} instance to use
* @return {@link ZonedDateTime} with the next greater field incremented and all smaller fields cleared
*/
abstract ZonedDateTime overflow(ZonedDateTime dateTime);
}
private final String expr;
private final SimpleField secondField;
private final SimpleField minuteField;
private final SimpleField hourField;
private final DayOfWeekField dayOfWeekField;
private final SimpleField monthField;
private final DayOfMonthField dayOfMonthField;
public CronExpression(final String expr) {
this(expr, true);
}
public CronExpression(final String expr, final boolean withSeconds) {
if (expr == null) {
throw new IllegalArgumentException("expr is null"); //$NON-NLS-1$
}
this.expr = expr;
final int expectedParts = withSeconds ? 6 : 5;
final String[] parts = expr.split("\\s+"); //$NON-NLS-1$
if (parts.length != expectedParts) {
throw new IllegalArgumentException(String.format("Invalid cron expression [%s], expected %s field, got %s", expr, expectedParts, parts.length));
}
int ix = withSeconds ? 1 : 0;
this.secondField = new SimpleField(CronFieldType.SECOND, withSeconds ? parts[0] : "0");
this.minuteField = new SimpleField(CronFieldType.MINUTE, parts[ix++]);
this.hourField = new SimpleField(CronFieldType.HOUR, parts[ix++]);
this.dayOfMonthField = new DayOfMonthField(parts[ix++]);
this.monthField = new SimpleField(CronFieldType.MONTH, parts[ix++]);
this.dayOfWeekField = new DayOfWeekField(parts[ix]);
}
public static CronExpression create(final String expr) {
return new CronExpression(expr, true);
}
public static CronExpression createWithoutSeconds(final String expr) {
return new CronExpression(expr, false);
}
public ZonedDateTime nextTimeAfter(ZonedDateTime afterTime) {
// will search for the next time within the next 4 years. If there is no
// time matching, an InvalidArgumentException will be thrown (it is very
// likely that the cron expression is invalid, like the February 30th).
return nextTimeAfter(afterTime, afterTime.plusYears(4));
}
public LocalDateTime nextLocalDateTimeAfter(LocalDateTime dateTime) {
return nextTimeAfter(ZonedDateTime.of(dateTime, ZoneId.systemDefault())).toLocalDateTime();
}
public ZonedDateTime nextTimeAfter(ZonedDateTime afterTime, long durationInMillis) {
// will search for the next time within the next durationInMillis
// millisecond. Be aware that the duration is specified in millis,
// but in fact the limit is checked on a day-to-day basis.
return nextTimeAfter(afterTime, afterTime.plus(Duration.ofMillis(durationInMillis)));
}
public ZonedDateTime nextTimeAfter(ZonedDateTime afterTime, ZonedDateTime dateTimeBarrier) {
ZonedDateTime[] nextDateTime = { afterTime.plusSeconds(1).withNano(0) };
while (true) {
checkIfDateTimeBarrierIsReached(nextDateTime[0], dateTimeBarrier);
if (!monthField.nextMatch(nextDateTime)) {
continue;
}
if (!findDay(nextDateTime)) {
continue;
}
if (!hourField.nextMatch(nextDateTime)) {
continue;
}
if (!minuteField.nextMatch(nextDateTime)) {
continue;
}
if (!secondField.nextMatch(nextDateTime)) {
continue;
}
checkIfDateTimeBarrierIsReached(nextDateTime[0], dateTimeBarrier);
return nextDateTime[0];
}
}
/**
* Find the next match for the day field.
* <p>
* This is handled different than all other fields because there are two ways to describe the day and it is easier
* to handle them together in the same method.
*
* @param dateTime Initial {@link ZonedDateTime} instance to start from
* @return {@code true} if a match was found for this field or {@code false} if the field overflowed
* @see SimpleField#nextMatch(ZonedDateTime[])
*/
private boolean findDay(ZonedDateTime[] dateTime) {
int month = dateTime[0].getMonthValue();
while (!(dayOfMonthField.matches(dateTime[0].toLocalDate())
&& dayOfWeekField.matches(dateTime[0].toLocalDate()))) {
dateTime[0] = dateTime[0].plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
if (dateTime[0].getMonthValue() != month) {
return false;
}
}
return true;
}
private static void checkIfDateTimeBarrierIsReached(ZonedDateTime nextTime, ZonedDateTime dateTimeBarrier) {
if (nextTime.isAfter(dateTimeBarrier)) {
throw new IllegalArgumentException("No next execution time could be determined that is before the limit of " + dateTimeBarrier);
}
}
@Override
public String toString() {
return getClass().getSimpleName() + "<" + expr + ">";
}
static class FieldPart implements Comparable<FieldPart> {
private int from = -1, to = -1, increment = -1;
private String modifier, incrementModifier;
@Override
public int compareTo(FieldPart o) {
return Integer.compare(from, o.from);
}
}
abstract static class BasicField {
@SuppressWarnings({"RegExpRepeatedSpace", "RegExpSimplifiable", "RegExpSingleCharAlternation", "RegExpRedundantEscape"})
private static final Pattern CRON_FIELD_REGEXP = Pattern
.compile("""
(?: # start of group 1
(?:(?<all>\\*)|(?<ignore>\\?)|(?<last>L)) # global flag (L, ?, *)
| (?<start>[0-9]{1,2}|[a-z]{3,3}) # or start number or symbol
(?: # start of group 2
(?<mod>L|W) # modifier (L,W)
| -(?<end>[0-9]{1,2}|[a-z]{3,3}) # or end nummer or symbol (in range)
)? # end of group 2
) # end of group 1
(?:(?<incmod>/|\\#)(?<inc>[0-9]{1,7}))? # increment and increment modifier (/ or \\#)
""",
Pattern.CASE_INSENSITIVE | Pattern.COMMENTS);
final CronFieldType fieldType;
final List<FieldPart> parts = new ArrayList<>();
private BasicField(CronFieldType fieldType, String fieldExpr) {
this.fieldType = fieldType;
parse(fieldExpr);
}
private void parse(String fieldExpr) { // NOSONAR
String[] rangeParts = fieldExpr.split(",");
for (String rangePart : rangeParts) {
Matcher m = CRON_FIELD_REGEXP.matcher(rangePart);
if (!m.matches()) {
throw new IllegalArgumentException("Invalid cron field '" + rangePart + "' for field [" + fieldType + "]");
}
String startNummer = m.group("start");
String modifier = m.group("mod");
String sluttNummer = m.group("end");
String incrementModifier = m.group("incmod");
String increment = m.group("inc");
FieldPart part = new FieldPart();
part.increment = 999;
if (startNummer != null) {
part.from = mapValue(startNummer);
part.modifier = modifier;
if (sluttNummer != null) {
part.to = mapValue(sluttNummer);
part.increment = 1;
} else if (increment != null) {
part.to = fieldType.to;
} else {
part.to = part.from;
}
} else if (m.group("all") != null) {
part.from = fieldType.from;
part.to = fieldType.to;
part.increment = 1;
} else if (m.group("ignore") != null) {
part.modifier = m.group("ignore");
} else if (m.group("last") != null) {
part.modifier = m.group("last");
} else {
throw new IllegalArgumentException("Invalid cron part: " + rangePart);
}
if (increment != null) {
part.incrementModifier = incrementModifier;
part.increment = Integer.parseInt(increment);
}
validateRange(part);
validatePart(part);
parts.add(part);
}
Collections.sort(parts);
}
protected void validatePart(FieldPart part) {
if (part.modifier != null) {
throw new IllegalArgumentException(String.format("Invalid modifier [%s]", part.modifier));
} else if (part.incrementModifier != null && !"/".equals(part.incrementModifier)) {
throw new IllegalArgumentException(String.format("Invalid increment modifier [%s]", part.incrementModifier));
}
}
private void validateRange(FieldPart part) {
if ((part.from != -1 && part.from < fieldType.from) || (part.to != -1 && part.to > fieldType.to)) {
throw new IllegalArgumentException(String.format("Invalid interval [%s-%s], must be %s<=_<=%s", part.from, part.to, fieldType.from,
fieldType.to));
} else if (part.from != -1 && part.to != -1 && part.from > part.to) {
throw new IllegalArgumentException(
String.format(
"Invalid interval [%s-%s]. Rolling periods are not supported (ex. 5-1, only 1-5) since this won't give a deterministic result. Must be %s<=_<=%s",
part.from, part.to, fieldType.from, fieldType.to));
}
}
protected int mapValue(String value) {
int idx;
if (fieldType.names != null && (idx = fieldType.names.indexOf(value.toUpperCase(Locale.getDefault()))) >= 0) {
return idx + fieldType.from;
}
return Integer.parseInt(value);
}
protected boolean matches(int val, FieldPart part) {
return val >= part.from && val <= part.to && (val - part.from) % part.increment == 0;
}
protected int nextMatch(int val, FieldPart part) {
if (val > part.to) {
return -1;
}
int nextPotential = Math.max(val, part.from);
if (part.increment == 1 || nextPotential == part.from) {
return nextPotential;
}
int remainder = ((nextPotential - part.from) % part.increment);
if (remainder != 0) {
nextPotential += part.increment - remainder;
}
return nextPotential <= part.to ? nextPotential : -1;
}
}
static class SimpleField extends BasicField {
SimpleField(CronFieldType fieldType, String fieldExpr) {
super(fieldType, fieldExpr);
}
/**
* Find the next match for this field. If a match cannot be found force an overflow and increase the next
* greatest field.
*
* @param dateTime {@link ZonedDateTime} array so the reference can be modified
* @return {@code true} if a match was found for this field or {@code false} if the field overflowed
*/
protected boolean nextMatch(ZonedDateTime[] dateTime) {
int value = fieldType.getValue(dateTime[0]);
for (FieldPart part : parts) {
int nextMatch = nextMatch(value, part);
if (nextMatch > -1) {
if (nextMatch != value) {
dateTime[0] = fieldType.setValue(dateTime[0], nextMatch);
}
return true;
}
}
dateTime[0] = fieldType.overflow(dateTime[0]);
return false;
}
}
static class DayOfWeekField extends BasicField {
DayOfWeekField(String fieldExpr) {
super(CronFieldType.DAY_OF_WEEK, fieldExpr);
}
boolean matches(LocalDate dato) {
for (FieldPart part : parts) {
if ("L".equals(part.modifier)) {
YearMonth ym = YearMonth.of(dato.getYear(), dato.getMonth().getValue());
return dato.getDayOfWeek() == DayOfWeek.of(part.from) && dato.getDayOfMonth() > (ym.lengthOfMonth() - 7);
} else if ("#".equals(part.incrementModifier)) {
if (dato.getDayOfWeek() == DayOfWeek.of(part.from)) {
int num = dato.getDayOfMonth() / 7;
return part.increment == (dato.getDayOfMonth() % 7 == 0 ? num : num + 1);
}
return false;
} else if (matches(dato.getDayOfWeek().getValue(), part)) {
return true;
}
}
return false;
}
@Override
protected int mapValue(String value) {
// Use 1-7 for weedays, but 0 will also represent sunday (linux practice)
return "0".equals(value) ? 7 : super.mapValue(value);
}
@Override
protected boolean matches(int val, FieldPart part) {
return "?".equals(part.modifier) || super.matches(val, part);
}
@Override
protected void validatePart(FieldPart part) {
if (part.modifier != null && !Arrays.asList("L", "?").contains(part.modifier)) {
throw new IllegalArgumentException(String.format("Invalid modifier [%s]", part.modifier));
} else if (part.incrementModifier != null && !Arrays.asList("/", "#").contains(part.incrementModifier)) {
throw new IllegalArgumentException(String.format("Invalid increment modifier [%s]", part.incrementModifier));
}
}
}
static class DayOfMonthField extends BasicField {
DayOfMonthField(String fieldExpr) {
super(CronFieldType.DAY_OF_MONTH, fieldExpr);
}
boolean matches(LocalDate dato) {
for (FieldPart part : parts) {
if ("L".equals(part.modifier)) {
YearMonth ym = YearMonth.of(dato.getYear(), dato.getMonth().getValue());
return dato.getDayOfMonth() == (ym.lengthOfMonth() - (part.from == -1 ? 0 : part.from));
} else if ("W".equals(part.modifier)) {
if (dato.getDayOfWeek().getValue() <= 5) {
if (dato.getDayOfMonth() == part.from) {
return true;
} else if (dato.getDayOfWeek().getValue() == 5) {
return dato.plusDays(1).getDayOfMonth() == part.from;
} else if (dato.getDayOfWeek().getValue() == 1) {
return dato.minusDays(1).getDayOfMonth() == part.from;
}
}
} else if (matches(dato.getDayOfMonth(), part)) {
return true;
}
}
return false;
}
@Override
protected void validatePart(FieldPart part) {
if (part.modifier != null && !Arrays.asList("L", "W", "?").contains(part.modifier)) {
throw new IllegalArgumentException(String.format("Invalid modifier [%s]", part.modifier));
} else if (part.incrementModifier != null && !"/".equals(part.incrementModifier)) {
throw new IllegalArgumentException(String.format("Invalid increment modifier [%s]", part.incrementModifier));
}
}
@Override
protected boolean matches(int val, FieldPart part) {
return "?".equals(part.modifier) || super.matches(val, part);
}
}
}

View File

@@ -1,52 +0,0 @@
package fr.pandacube.lib.core.util;
import java.text.DecimalFormat;
import java.util.Arrays;
public class DistanceUtil {
public static String distanceToString(double meterDist, int precision, DistanceUnit... desiredUnits) {
Arrays.sort(desiredUnits);
DistanceUnit choosenUnit = desiredUnits[0]; // la plus petite unitée
for (DistanceUnit unit : desiredUnits) {
if (meterDist / unit.multiplicator < 1) continue;
choosenUnit = unit;
}
if (choosenUnit != desiredUnits[0] && precision <= 2) precision = 2;
String precisionFormat = "##0";
if (precision > 0) precisionFormat += ".";
precisionFormat += "0".repeat(precision);
DecimalFormat df = new DecimalFormat(precisionFormat);
double dist = meterDist / choosenUnit.multiplicator;
return df.format(dist) + choosenUnit.unitStr;
}
public static String distanceToString(double meterDist, int precision) {
return distanceToString(meterDist, precision, DistanceUnit.M, DistanceUnit.KM);
}
public enum DistanceUnit {
NM(0.000000001, "nm"),
UM(0.000001, "µm"),
MM(0.001, "mm"),
CM(0.01, "cm"),
M(1, "m"),
KM(1000, "km");
private final double multiplicator;
private final String unitStr;
DistanceUnit(double mult, String s) {
multiplicator = mult;
unitStr = s;
}
}
}

View File

@@ -1,85 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.Arrays;
import java.util.stream.Collectors;
public class EnumUtil {
/**
* List all enum constants which are in the specified enum class.
*
* @param enumType the enum class.
* @param separator a string which will be used as a separator
* @return a string representation of the enum class.
*/
public static <T extends Enum<T>> String enumList(Class<T> enumType, String separator) {
return Arrays.stream(enumType.getEnumConstants())
.map(Enum::name)
.collect(Collectors.joining(separator));
}
/**
* List all enum constants which are in the specified enum class. It is
* equivalent to call
* {@link #enumList(Class, String)} with the second parameter
* <code>", "</code>
*
* @param enumType the enum class.
* @return a string representation of the enum class.
*/
public static <T extends Enum<T>> String enumList(Class<T> enumType) {
return enumList(enumType, ", ");
}
/**
* Permet de rechercher l'existance d'un élément dans un enum, de façon
* insensible à la casse
*
* @param enumType la classe correpondant à l'enum à lister
* @param search l'élément à rechercher, insensible à la casse
* @return l'élément de l'énumarétion, si elle a été trouvée, null sinon
*/
public static <T extends Enum<T>> T searchEnum(Class<T> enumType, String search) {
T[] elements = enumType.getEnumConstants();
for (T el : elements)
if (el.name().equalsIgnoreCase(search)) return el;
return null;
}
/**
* Permet de rechercher l'existance d'un élément dans un enum, de façon
* insensible à la casse
* La validité de la classe passé en premier paramètre est vérifiée
* dynamiquement et non
* statiquement. Préférez l'utilisation de
* {@link #searchEnum(Class, String)} quand c'est possible.
*
* @param enumType la classe correpondant à l'enum à lister
* @param search l'élément à rechercher, insensible à la casse
* @return l'élément de l'énumération, si elle a été trouvée et si la classe
* passée en paramètre est un enum, null dans les autres cas
*/
public static Enum<?> searchUncheckedEnum(Class<?> enumType, String search) {
if (!enumType.isEnum()) return null;
Enum<?>[] elements = (Enum<?>[]) enumType.getEnumConstants();
for (Enum<?> el : elements)
if (el.name().equalsIgnoreCase(search)) return el;
return null;
}
/**
* Retourne une valeur aléatoire parmis les élément de l'Enum spécifié en
* paramètre.
*
* @param enumType l'enum dans lequel piocher la valeur
* @return une des valeurs de l'enum
*/
public static <T extends Enum<T>> T randomValue(Class<T> enumType) {
T[] elements = enumType.getEnumConstants();
return elements[RandomUtil.rand.nextInt(elements.length)];
}
}

View File

@@ -1,38 +0,0 @@
package fr.pandacube.lib.core.util;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
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);
}
BasicFileAttributes sourceAttr = Files.readAttributes(source.toPath(), BasicFileAttributes.class);
if (sourceAttr.isDirectory()) {
if (target.mkdir()) {
for (String child : source.list())
copy(new File(source, child), new File(target, child));
}
else {
throw new IOException("Cannot create directory " + target);
}
}
else if (sourceAttr.isRegularFile()) {
Files.copy(source.toPath(), target.toPath());
}
}
}

View File

@@ -1,773 +0,0 @@
package fr.pandacube.lib.core.util;
import java.net.URL;
import java.util.ArrayList;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Class GifDecoder - Decodes a GIF file into one or more frames.
* <br><pre>
* Example:
* GifDecoder d = new GifDecoder();
* d.read("sample.gif");
* int n = d.getFrameCount();
* for (int i = 0; i < n; i++) {
* BufferedImage frame = d.getFrame(i); // frame i
* int t = d.getDelay(i); // display duration of frame in milliseconds
* // do something with frame
* }
* </pre>
* No copyright asserted on the source code of this class. May be used for
* any purpose, however, refer to the Unisys LZW patent for any additional
* restrictions. Please forward any corrections to questions at fmsware.com.
*
* @author Kevin Weiner, FM Software; LZW decoder adapted from John Cristy's ImageMagick.
* @version 1.03 November 2003
*
*/
public class GifDecoder {
/**
* File read status: No errors.
*/
public static final int STATUS_OK = 0;
/**
* File read status: Error decoding file (may be partially decoded)
*/
public static final int STATUS_FORMAT_ERROR = 1;
/**
* File read status: Unable to open source.
*/
public static final int STATUS_OPEN_ERROR = 2;
protected BufferedInputStream in;
protected int status;
protected int width; // full image width
protected int height; // full image height
protected boolean gctFlag; // global color table used
protected int gctSize; // size of global color table
protected int loopCount = 1; // iterations; 0 = repeat forever
protected int[] gct; // global color table
protected int[] lct; // local color table
protected int[] act; // active color table
protected int bgIndex; // background color index
protected int bgColor; // background color
protected int lastBgColor; // previous bg color
protected int pixelAspect; // pixel aspect ratio
protected boolean lctFlag; // local color table flag
protected boolean interlace; // interlace flag
protected int lctSize; // local color table size
protected int ix, iy, iw, ih; // current image rectangle
protected Rectangle lastRect; // last image rect
protected BufferedImage image; // current frame
protected BufferedImage lastImage; // previous frame
protected byte[] block = new byte[256]; // current data block
protected int blockSize = 0; // block size
// last graphic control extension info
protected int dispose = 0;
// 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
protected int lastDispose = 0;
protected boolean transparency = false; // use transparent color
protected int delay = 0; // delay in milliseconds
protected int transIndex; // transparent color index
protected static final int MaxStackSize = 4096;
// max decoder pixel stack size
// LZW decoder working arrays
protected short[] prefix;
protected byte[] suffix;
protected byte[] pixelStack;
protected byte[] pixels;
protected ArrayList<GifFrame> frames; // frames read from current file
protected int frameCount;
record GifFrame(BufferedImage image, int delay) {
}
/**
* Gets display duration for specified frame.
*
* @param n int index of frame
* @return delay in milliseconds
*/
public int getDelay(int n) {
//
delay = -1;
if ((n >= 0) && (n < frameCount)) {
delay = frames.get(n).delay;
}
return delay;
}
/**
* Gets the number of frames read from file.
* @return frame count
*/
public int getFrameCount() {
return frameCount;
}
/**
* Gets the first (or only) image read.
*
* @return BufferedImage containing first frame, or null if none.
*/
public BufferedImage getImage() {
return getFrame(0);
}
/**
* Gets the "Netscape" iteration count, if any.
* A count of 0 means repeat indefinitiely.
*
* @return iteration count if one was specified, else 1.
*/
public int getLoopCount() {
return loopCount;
}
/**
* Creates new frame image from current data (and previous
* frames as specified by their disposition codes).
*/
protected void setPixels() {
// expose destination image's pixels as int array
int[] dest =
((DataBufferInt) image.getRaster().getDataBuffer()).getData();
// fill in starting image contents based on last image's dispose code
if (lastDispose > 0) {
if (lastDispose == 3) {
// use image before last
int n = frameCount - 2;
if (n > 0) {
lastImage = getFrame(n - 1);
} else {
lastImage = null;
}
}
if (lastImage != null) {
int[] prev =
((DataBufferInt) lastImage.getRaster().getDataBuffer()).getData();
System.arraycopy(prev, 0, dest, 0, width * height);
// copy pixels
if (lastDispose == 2) {
// fill last image rect area with background color
Graphics2D g = image.createGraphics();
Color c = transparency ? new Color(0, 0, 0, 0) : new Color(lastBgColor);
g.setColor(c);
g.setComposite(AlphaComposite.Src); // replace area
g.fill(lastRect);
g.dispose();
}
}
}
// copy each source line to the appropriate place in the destination
int pass = 1;
int inc = 8;
int iline = 0;
for (int i = 0; i < ih; i++) {
int line = i;
if (interlace) {
if (iline >= ih) {
pass++;
switch (pass) {
case 2 :
iline = 4;
break;
case 3 :
iline = 2;
inc = 4;
break;
case 4 :
iline = 1;
inc = 2;
}
}
line = iline;
iline += inc;
}
line += iy;
if (line < height) {
int k = line * width;
int dx = k + ix; // start of line in dest
int dlim = dx + iw; // end of dest line
if ((k + width) < dlim) {
dlim = k + width; // past dest edge
}
int sx = i * iw; // start of line in source
while (dx < dlim) {
// map color and insert in destination
int index = (pixels[sx++]) & 0xff;
int c = act[index];
if (c != 0) {
dest[dx] = c;
}
dx++;
}
}
}
}
/**
* Gets the image contents of frame n.
*
* @return BufferedImage representation of frame, or null if n is invalid.
*/
public BufferedImage getFrame(int n) {
BufferedImage im = null;
if ((n >= 0) && (n < frameCount)) {
im = frames.get(n).image;
}
return im;
}
/**
* Gets image size.
*
* @return GIF image dimensions
*/
public Dimension getFrameSize() {
return new Dimension(width, height);
}
/**
* Reads GIF image from stream
*
* @param is containing GIF file.
* @return read status code (0 = no errors)
*/
public int read(BufferedInputStream is) {
init();
if (is != null) {
in = is;
readHeader();
if (!err()) {
readContents();
if (frameCount < 0) {
status = STATUS_FORMAT_ERROR;
}
}
try {
is.close();
} catch (IOException ignored) {
}
} else {
status = STATUS_OPEN_ERROR;
}
return status;
}
/**
* Reads GIF image from stream
*
* @param is containing GIF file.
* @return read status code (0 = no errors)
*/
public int read(InputStream is) {
init();
if (is != null) {
if (!(is instanceof BufferedInputStream))
is = new BufferedInputStream(is);
in = (BufferedInputStream) is;
readHeader();
if (!err()) {
readContents();
if (frameCount < 0) {
status = STATUS_FORMAT_ERROR;
}
}
try {
is.close();
} catch (IOException ignored) {
}
} else {
status = STATUS_OPEN_ERROR;
}
return status;
}
/**
* Reads GIF file from specified file/URL source
* (URL assumed if name contains ":/" or "file:")
*
* @param name String containing source
* @return read status code (0 = no errors)
*/
public int read(String name) {
status = STATUS_OK;
try {
name = name.trim().toLowerCase();
if ((name.contains("file:")) ||
(name.indexOf(":/") > 0)) {
URL url = new URL(name);
in = new BufferedInputStream(url.openStream());
} else {
in = new BufferedInputStream(new FileInputStream(name));
}
status = read(in);
} catch (IOException e) {
status = STATUS_OPEN_ERROR;
}
return status;
}
/**
* Decodes LZW image data into pixel array.
* Adapted from John Cristy's ImageMagick.
*/
protected void decodeImageData() {
int NullCode = -1;
int npix = iw * ih;
int available,
clear,
code_mask,
code_size,
end_of_information,
in_code,
old_code,
bits,
code,
count,
i,
datum,
data_size,
first,
top,
bi,
pi;
if ((pixels == null) || (pixels.length < npix)) {
pixels = new byte[npix]; // allocate new pixel array
}
if (prefix == null) prefix = new short[MaxStackSize];
if (suffix == null) suffix = new byte[MaxStackSize];
if (pixelStack == null) pixelStack = new byte[MaxStackSize + 1];
// Initialize GIF data stream decoder.
data_size = read();
clear = 1 << data_size;
end_of_information = clear + 1;
available = clear + 2;
old_code = NullCode;
code_size = data_size + 1;
code_mask = (1 << code_size) - 1;
for (code = 0; code < clear; code++) {
prefix[code] = 0;
suffix[code] = (byte) code;
}
// Decode GIF pixel stream.
datum = bits = count = first = top = pi = bi = 0;
for (i = 0; i < npix;) {
if (top == 0) {
if (bits < code_size) {
// Load bytes until there are enough bits for a code.
if (count == 0) {
// Read a new data block.
count = readBlock();
if (count <= 0)
break;
bi = 0;
}
datum += ((block[bi]) & 0xff) << bits;
bits += 8;
bi++;
count--;
continue;
}
// Get the next code.
code = datum & code_mask;
datum >>= code_size;
bits -= code_size;
// Interpret the code
if ((code > available) || (code == end_of_information))
break;
if (code == clear) {
// Reset decoder.
code_size = data_size + 1;
code_mask = (1 << code_size) - 1;
available = clear + 2;
old_code = NullCode;
continue;
}
if (old_code == NullCode) {
pixelStack[top++] = suffix[code];
old_code = code;
first = code;
continue;
}
in_code = code;
if (code == available) {
pixelStack[top++] = (byte) first;
code = old_code;
}
while (code > clear) {
pixelStack[top++] = suffix[code];
code = prefix[code];
}
first = (suffix[code]) & 0xff;
// Add a new string to the string table,
if (available >= MaxStackSize) {
pixelStack[top++] = (byte) first;
continue;
}
pixelStack[top++] = (byte) first;
prefix[available] = (short) old_code;
suffix[available] = (byte) first;
available++;
if (((available & code_mask) == 0)
&& (available < MaxStackSize)) {
code_size++;
code_mask += available;
}
old_code = in_code;
}
// Pop a pixel off the pixel stack.
top--;
pixels[pi++] = pixelStack[top];
i++;
}
for (i = pi; i < npix; i++) {
pixels[i] = 0; // clear missing pixels
}
}
/**
* Returns true if an error was encountered during reading/decoding
*/
protected boolean err() {
return status != STATUS_OK;
}
/**
* Initializes or re-initializes reader
*/
protected void init() {
status = STATUS_OK;
frameCount = 0;
frames = new ArrayList<>();
gct = null;
lct = null;
}
/**
* Reads a single byte from the input stream.
*/
protected int read() {
int curByte = 0;
try {
curByte = in.read();
} catch (IOException e) {
status = STATUS_FORMAT_ERROR;
}
return curByte;
}
/**
* Reads next variable length block from input.
*
* @return number of bytes stored in "buffer"
*/
protected int readBlock() {
blockSize = read();
int n = 0;
if (blockSize > 0) {
try {
int count;
while (n < blockSize) {
count = in.read(block, n, blockSize - n);
if (count == -1)
break;
n += count;
}
} catch (IOException ignored) {
}
if (n < blockSize) {
status = STATUS_FORMAT_ERROR;
}
}
return n;
}
/**
* Reads color table as 256 RGB integer values
*
* @param ncolors int number of colors to read
* @return int array containing 256 colors (packed ARGB with full alpha)
*/
protected int[] readColorTable(int ncolors) {
int nbytes = 3 * ncolors;
int[] tab = null;
byte[] c = new byte[nbytes];
int n = 0;
try {
n = in.read(c);
} catch (IOException ignored) {
}
if (n < nbytes) {
status = STATUS_FORMAT_ERROR;
} else {
tab = new int[256]; // max size to avoid bounds checks
int i = 0;
int j = 0;
while (i < ncolors) {
int r = (c[j++]) & 0xff;
int g = (c[j++]) & 0xff;
int b = (c[j++]) & 0xff;
tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
}
}
return tab;
}
/**
* Main file parser. Reads GIF content blocks.
*/
protected void readContents() {
// read GIF file content blocks
boolean done = false;
while (!(done || err())) {
int code = read();
switch (code) {
case 0x2C : // image separator
readImage();
break;
case 0x21 : // extension
code = read();
switch (code) {
case 0xf9 : // graphics control extension
readGraphicControlExt();
break;
case 0xff : // application extension
readBlock();
StringBuilder app = new StringBuilder();
for (int i = 0; i < 11; i++) {
app.append((char) block[i]);
}
if (app.toString().equals("NETSCAPE2.0")) {
readNetscapeExt();
}
else
skip(); // don't care
break;
default : // uninteresting extension
skip();
}
break;
case 0x3b : // terminator
done = true;
break;
case 0x00 : // bad byte, but keep going and see what happens
break;
default :
status = STATUS_FORMAT_ERROR;
}
}
}
/**
* Reads Graphics Control Extension values
*/
protected void readGraphicControlExt() {
read(); // block size
int packed = read(); // packed fields
dispose = (packed & 0x1c) >> 2; // disposal method
if (dispose == 0) {
dispose = 1; // elect to keep old image if discretionary
}
transparency = (packed & 1) != 0;
delay = readShort() * 10; // delay in milliseconds
transIndex = read(); // transparent color index
read(); // block terminator
}
/**
* Reads GIF file header information.
*/
protected void readHeader() {
StringBuilder id = new StringBuilder();
for (int i = 0; i < 6; i++) {
id.append((char) read());
}
if (!id.toString().startsWith("GIF")) {
status = STATUS_FORMAT_ERROR;
return;
}
readLSD();
if (gctFlag && !err()) {
gct = readColorTable(gctSize);
bgColor = gct[bgIndex];
}
}
/**
* Reads next frame image
*/
protected void readImage() {
ix = readShort(); // (sub)image position & size
iy = readShort();
iw = readShort();
ih = readShort();
int packed = read();
lctFlag = (packed & 0x80) != 0; // 1 - local color table flag
interlace = (packed & 0x40) != 0; // 2 - interlace flag
// 3 - sort flag
// 4-5 - reserved
lctSize = 2 << (packed & 7); // 6-8 - local color table size
if (lctFlag) {
lct = readColorTable(lctSize); // read table
act = lct; // make local table active
} else {
act = gct; // make global table active
if (bgIndex == transIndex)
bgColor = 0;
}
int save = 0;
if (transparency) {
save = act[transIndex];
act[transIndex] = 0; // set transparent color if specified
}
if (act == null) {
status = STATUS_FORMAT_ERROR; // no color table defined
}
if (err()) return;
decodeImageData(); // decode pixel data
skip();
if (err()) return;
frameCount++;
// create new image to receive frame data
image =
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
setPixels(); // transfer pixel data to image
frames.add(new GifFrame(image, delay)); // add image to frame list
if (transparency) {
act[transIndex] = save;
}
resetFrame();
}
/**
* Reads Logical Screen Descriptor
*/
protected void readLSD() {
// logical screen size
width = readShort();
height = readShort();
// packed fields
int packed = read();
gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
// 2-4 : color resolution
// 5 : gct sort flag
gctSize = 2 << (packed & 7); // 6-8 : gct size
bgIndex = read(); // background color index
pixelAspect = read(); // pixel aspect ratio
}
/**
* Reads Netscape extenstion to obtain iteration count
*/
protected void readNetscapeExt() {
do {
readBlock();
if (block[0] == 1) {
// loop count sub-block
int b1 = (block[1]) & 0xff;
int b2 = (block[2]) & 0xff;
loopCount = (b2 << 8) | b1;
}
} while ((blockSize > 0) && !err());
}
/**
* Reads next 16-bit value, LSB first
*/
protected int readShort() {
// read 16-bit value, LSB first
return read() | (read() << 8);
}
/**
* Resets frame state for reading next image.
*/
protected void resetFrame() {
lastDispose = dispose;
lastRect = new Rectangle(ix, iy, iw, ih);
lastImage = image;
lastBgColor = bgColor;
lct = null;
}
/**
* Skips variable length blocks up to and including
* next zero length block.
*/
protected void skip() {
do {
readBlock();
} while ((blockSize > 0) && !err());
}
}

View File

@@ -1,74 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class IteratorIterator<T> implements Iterator<T> {
public static <T> IteratorIterator<T> ofCollectionOfIterable(Collection<Iterable<T>> coll) {
return new IteratorIterator<>(coll.stream().map(Iterable::iterator).iterator());
}
public static <T> IteratorIterator<T> ofCollectionOfIterator(Collection<Iterator<T>> coll) {
return new IteratorIterator<>(new ArrayList<>(coll).iterator());
}
@SafeVarargs
public static <T> IteratorIterator<T> ofArrayOfIterable(Iterable<T>... arr) {
return new IteratorIterator<>(Arrays.stream(arr).map(Iterable::iterator).iterator());
}
@SafeVarargs
public static <T> IteratorIterator<T> ofArrayOfIterator(Iterator<T>... arr) {
return new IteratorIterator<>(Arrays.asList(arr).iterator());
}
private final Iterator<Iterator<T>> iterators;
private Iterator<T> currentIterator = null;
private IteratorIterator(Iterator<Iterator<T>> iterators) {
this.iterators = iterators;
}
private void fixCurrentIterator() {
if (currentIterator != null && !currentIterator.hasNext()) {
currentIterator = null;
}
}
private void fixState() {
fixCurrentIterator();
while (currentIterator == null && iterators.hasNext()) {
currentIterator = iterators.next();
fixCurrentIterator();
}
}
@Override
public boolean hasNext() {
fixState();
return currentIterator != null && currentIterator.hasNext();
}
@Override
public T next() {
if (!hasNext())
throw new NoSuchElementException("No next value found in iterator.");
return currentIterator.next();
}
/**
* @implNote The current implementation of {@link IteratorIterator} may not support
* running this method if the current position is the last value of one of
* the underlying iterable, and if the {@link #hasNext()} method has been called before this one.
*/
@Override
public void remove() {
// TODO change code to avoid currentIterator being null because we are about to change currentIterator
if (currentIterator != null)
currentIterator.remove();
}
}

View File

@@ -1,736 +0,0 @@
package fr.pandacube.lib.core.util;
//******************************************************************************
//***
//*** INTERPRETEUR ARITHMETIQUE version 2.1 Version Java
//***
//***
//***
//******************************************************************************
/*
Historique:
2.1:
- Version Java disponible:
# les operateurs mathematiques disponibles sont ceux de Java donc certains manquent.
2.0:
- Portage en C++ et debut en Java
Version C++:
- Meilleure gestion memoire lors de la construction de l'expression.
- Acceleration de certaines operations.
Version Java:
- Premiere version. Normalement ca doit marcher
1.3b: ajoute les fonctions suivantes: (NON DISTRIBUEE)
- reconnaissance du symbole <20>
- ecriture formatee d'expression (<28> ameliorer)
1.2: corrige les bugs suivants:
- erreur sur l'interpretation de fonctions unaires imbriquees telles que ln(exp(x)).
- la fonction puissance (pour les puissances entieres).
ajoute:
- la verification de la chaine entree (voir code d'erreur)
- la verification de l'existence de l'evaluation (voir code d'erreur)
1.1: corrige un bug au niveau de l'interpretation des fonctions du type:
exp(-(x+y))
1.0: premiere version
Le code source peut etre librement modifie et distribue.
Puisqu'il s'agit d'un essai en Java, le code ne doit pas etre super genial et merite sans doute
quelques modifications.En particulier, il serait interessant de rajouter le support des Exceptions
pour les calculs (division par zero, etc...).
*/
// Classe servant à palier l'absence de passage par variables ou reference
class VariableInt {
public int mValue;
}
// Classe principale
public class JArithmeticInterpreter {
// Variables
final int mOperator;
final double mValue;
JArithmeticInterpreter fg, fd;
// Methods
// ....................................................................................
// Node
private JArithmeticInterpreter() {
this(0, 0);
}
// ....................................................................................
// Node
private JArithmeticInterpreter(int Operator, double Value) {
mOperator = Operator;
mValue = Value;
}
// ....................................................................................
// Construct_Tree
private static JArithmeticInterpreter constructTree(StringBuffer string, int length) {
int imbric, Bimbric;
int priorite, ope;
int position, positionv, i, j;
int opetemp = 0;
int espa = 0, espat = 0;
int caspp = 0;
JArithmeticInterpreter node;
// Initialisation des variables
if (length <= 0) {
return null;
}
ope = 0;
imbric = 0;
Bimbric = 128;
priorite = 6;
i = 0;
positionv = position = 0;
// Mise en place des donnees sur le morceau de chaine
while (i < length)
if (((string.charAt(i) > 47) && (string.charAt(i) < 58)) || (string.charAt(i) == '<27>')) {
if (priorite > 5) {
priorite = 5;
positionv = i;
}
i++;
}
else if ((string.charAt(i) > 96) && (string.charAt(i) < 117)) {
VariableInt Vopetemp, Vespat;
Vopetemp = new VariableInt();
Vespat = new VariableInt();
Vopetemp.mValue = opetemp;
Vespat.mValue = espat;
FindOperator(Vopetemp, Vespat, string, i);
opetemp = Vopetemp.mValue;
espat = Vespat.mValue;
if (opetemp >= 0) {
if (imbric < Bimbric) {
Bimbric = imbric;
ope = opetemp;
position = i;
priorite = 4;
espa = espat;
}
else if ((imbric == Bimbric) && (priorite >= 4)) {
ope = opetemp;
position = i;
priorite = 4;
espa = espat;
}
j = i + 1;
i += espat;
while (j < i)
j++;
}
else if (string.charAt(i) == 't') {
if (priorite == 6) ope = -1;
i++;
}
else if (string.charAt(i) == 'p') {
if (priorite == 6) ope = -2;
i++;
}
else if (string.charAt(i) == 'r') {
if (priorite == 6) ope = -2;
i++;
}
else if (string.charAt(i) == 'n') {
if (priorite == 6) ope = -1;
i++;
}
else {
return null;
}
}
else
switch (string.charAt(i)) {
case '(':
imbric++;
i++;
break;
case ')':
imbric--;
i++;
break;
case '+':
if (imbric < Bimbric) {
Bimbric = imbric;
priorite = 1;
ope = 1;
position = i;
caspp = 0;
}
else if (imbric == Bimbric) {
priorite = 1;
ope = 1;
position = i;
caspp = 0;
}
i++;
break;
case '-':
if (imbric < Bimbric) {
if ((i - 1) < 0) {
if (priorite > 5) {
priorite = 1;
position = i;
ope = 2;
Bimbric = imbric;
caspp = 1;
}
}
else if (string.charAt(i - 1) == '(') {
if (priorite > 1) {
priorite = 1;
position = i;
Bimbric = imbric;
caspp = 1;
ope = 2;
}
}
else {
Bimbric = imbric;
priorite = 1;
ope = 2;
position = i;
caspp = 0;
}
}
else if (imbric == Bimbric) if ((i - 1) < 0) {
if (priorite > 5) {
priorite = 1;
position = i;
ope = 2;
caspp = 1;
}
}
else if (string.charAt(i - 1) == '(') {
if (priorite > 1) {
priorite = 1;
position = i;
caspp = 1;
ope = 2;
}
}
else {
priorite = 1;
ope = 2;
position = i;
caspp = 0;
}
i++;
break;
case '*':
if (imbric < Bimbric) {
Bimbric = imbric;
priorite = 2;
ope = 3;
position = i;
}
else if ((imbric == Bimbric) && (priorite >= 2)) {
priorite = 2;
ope = 3;
position = i;
}
i++;
break;
case '/':
if (imbric < Bimbric) {
Bimbric = imbric;
priorite = 2;
ope = 4;
position = i;
}
else if ((imbric == Bimbric) && (priorite >= 2)) {
priorite = 2;
ope = 4;
position = i;
}
i++;
break;
case '^':
if (imbric < Bimbric) {
Bimbric = imbric;
priorite = 3;
ope = 5;
position = i;
}
else if ((imbric == Bimbric) && (priorite >= 3)) {
priorite = 3;
ope = 5;
position = i;
}
i++;
break;
case '.':
i++;
break;
default:
return null;
}
if (imbric != 0) {
return null;
}
// Traitement des donnees
if (priorite == 6) {
node = new JArithmeticInterpreter(ope, 0.0);
return node;
}
else if (caspp == 1) {
node = new JArithmeticInterpreter(2, 0);
node.fg = new JArithmeticInterpreter(0, 0);
node.fd = new JArithmeticInterpreter();
if ((length - position - 1 - Bimbric) == 0) { // argument absent
return null;
}
StringBuffer temp = CopyPartialString(string, (position + 1), (length - 1 - Bimbric));
node.fd = constructTree(temp, (length - position - 1 - Bimbric));
return node;
}
else if (priorite == 5) {
node = new JArithmeticInterpreter(0, calc_const(string, positionv));
return node;
}
else if (ope > 5) {
node = new JArithmeticInterpreter(ope, 0);
if ((length - position - espa - Bimbric) == 0) { // argument absent
return null;
}
StringBuffer temp = CopyPartialString(string, (position + espa), (length - 1));
node.fg = constructTree(temp, (length - position - espa - Bimbric));
return node;
}
else {
node = new JArithmeticInterpreter(ope, 0);
if ((position - Bimbric) == 0) { // argument absent
return null;
}
StringBuffer temp = CopyPartialString(string, Bimbric, (position - 1));
node.fg = constructTree(temp, (position - Bimbric));
if ((length - position - 1 - Bimbric) == 0) { // argument absent
return null;
}
temp = CopyPartialString(string, (position + 1), (length - 1 - Bimbric));
node.fd = constructTree(temp, (length - position - 1 - Bimbric));
return node;
}
}
// ....................................................................................
private double computeTree() {
if (mOperator == 0) return mValue;
double valueL = fg.computeTree();
double valueR = 0;
if (fd != null) valueR = fd.computeTree();
switch (mOperator) {
case 1: // +
return (valueL + valueR);
case 2: // -
return (valueL - valueR);
case 3: // *
return (valueL * valueR);
case 4: // -
if (valueR == 0) {
return 0;
}
return (valueL / valueR);
case 5: // ^
return Math.pow(valueL, valueR);
case 6: // exp
return Math.exp(valueL);
case 7: // ln
if (valueL <= 0) {
return 0;
}
return (Math.log(valueL) / Math.log(2));
case 8: // log
if (valueL <= 0) {
return 0;
}
return Math.log(valueL);
case 9: // sqrt
if (valueL < 0) {
return 0;
}
return Math.sqrt(valueL);
case 10: // abs
return Math.abs(valueL);
case 11:
return Math.sin(valueL); // sin
case 12:
return Math.cos(valueL); // cos
case 13:
return Math.tan(valueL); // tan
case 14:
return Math.asin(valueL); // asin
case 15:
return Math.acos(valueL); // acos
case 16:
return Math.atan(valueL); // atan
default:
return 0;
}
}
// ....................................................................................
// Write_Tree
private void writeTree(StringBuffer string) {
boolean parenthese = false;
switch (mOperator) {
case 0:
string.append(StringUtil.formatDouble(mValue));
break;
case 1:
fg.writeTree(string);
string.append('+');
fd.writeTree(string);
break;
case 2:
if (fg.mOperator != 0 || fg.mValue != 0)
fg.writeTree(string);
string.append('-');
if ((fd.mOperator == 1) || (fd.mOperator == 2)) {
parenthese = true;
string.append('(');
}
fd.writeTree(string);
if (parenthese) string.append(')');
break;
case 3:
if ((fg.mOperator == 1) || (fg.mOperator == 2)) {
parenthese = true;
string.append('(');
}
fg.writeTree(string);
if (parenthese) string.append(')');
parenthese = false;
string.append('*');
if ((fd.mOperator == 1) || (fd.mOperator == 2)) {
parenthese = true;
string.append('(');
}
fd.writeTree(string);
if (parenthese) string.append(')');
break;
case 4:
if ((fg.mOperator == 1) || (fg.mOperator == 2)) {
parenthese = true;
string.append('(');
}
fg.writeTree(string);
if (parenthese) string.append(')');
parenthese = false;
string.append('/');
if ((fd.mOperator > 0) && (fd.mOperator < 5)) {
parenthese = true;
string.append('(');
}
fd.writeTree(string);
if (parenthese) string.append(')');
break;
case 5:
if ((fg.mOperator > 0) && (fg.mOperator < 5)) {
parenthese = true;
string.append('(');
}
fg.writeTree(string);
if (parenthese) string.append(')');
parenthese = false;
string.append('^');
if ((fd.mOperator > 0) && (fd.mOperator < 5)) {
parenthese = true;
string.append('(');
}
fd.writeTree(string);
if (parenthese) string.append(')');
break;
case 6:
string.append("exp(");
fg.writeTree(string);
string.append(')');
break;
case 7:
string.append("log(");
fg.writeTree(string);
string.append(')');
break;
case 8:
string.append("ln(");
fg.writeTree(string);
string.append(')');
break;
case 9:
string.append("sqrt(");
fg.writeTree(string);
string.append(')');
break;
case 10:
string.append("|");
fg.writeTree(string);
string.append('|');
break;
case 11:
string.append("sin(");
fg.writeTree(string);
string.append(')');
break;
case 12:
string.append("cos(");
fg.writeTree(string);
string.append(')');
break;
case 13:
string.append("tan(");
fg.writeTree(string);
string.append(')');
break;
case 14:
string.append("asin(");
fg.writeTree(string);
string.append(')');
break;
case 15:
string.append("acos(");
fg.writeTree(string);
string.append(')');
break;
case 16:
string.append("atan(");
fg.writeTree(string);
string.append(')');
break;
}
}
// ....................................................................................
// calc_const
private static double calc_const(StringBuffer chaine, int pos) {
int i = pos, j;
double temp = 0;
int signe = 1;
int longueur = chaine.length();
if (chaine.charAt(i) == '-') {
signe = -1;
i++;
}
if (chaine.charAt(i) == 'π') return signe * Math.PI;
while (i < longueur && chaine.charAt(i) > 47 && chaine.charAt(i) < 58) {
temp = temp * 10 + (chaine.charAt(i) - 48);
i++;
}
if (i < longueur && chaine.charAt(i) == '.') {
i++;
j = 1;
while (i < longueur && chaine.charAt(i) > 47 && chaine.charAt(i) < 58) {
temp = temp + (chaine.charAt(i) - 48) * Math.exp(-j * 2.30258509);
i++;
j++;
}
}
return (signe * temp);
}
// ....................................................................................
// FindOperator
private static void FindOperator(VariableInt oper, VariableInt esp, StringBuffer chaine, int pos) {
switch (chaine.charAt(pos)) {
case 'a':
switch (chaine.charAt(pos + 1)) {
case 'b':
esp.mValue = 3;
oper.mValue = 10;
break;
case 'c':
esp.mValue = 4;
oper.mValue = 15;
break;
case 's':
esp.mValue = 4;
oper.mValue = 14;
break;
case 't':
esp.mValue = 4;
oper.mValue = 16;
break;
}
break;
case 'c':
if (chaine.charAt(pos + 1) == 'h') {
esp.mValue = 2;
oper.mValue = 18;
}
else if ((chaine.charAt(pos + 1) == 'o') && (chaine.charAt(pos + 2) == 's'))
if (chaine.charAt(pos + 3) == 'h') {
esp.mValue = 4;
oper.mValue = 18;
}
else {
esp.mValue = 3;
oper.mValue = 12;
}
break;
case 'e':
if ((chaine.charAt(pos + 1) == 'x') && (chaine.charAt(pos + 2) == 'p')) {
esp.mValue = 3;
oper.mValue = 6;
}
else
oper.mValue = -10;
break;
case 'l':
if (chaine.charAt(pos + 1) == 'n') {
esp.mValue = 2;
oper.mValue = 7;
}
else if ((chaine.charAt(pos + 1) == 'o') && (chaine.charAt(pos + 2) == 'g')) {
esp.mValue = 3;
oper.mValue = 8;
}
else
oper.mValue = -10;
break;
case 's':
if (chaine.charAt(pos + 1) == 'h') {
esp.mValue = 2;
oper.mValue = 17;
}
else if (chaine.charAt(pos + 1) == 'q') {
esp.mValue = 4;
oper.mValue = 9;
}
else if (chaine.charAt(pos + 3) == 'h') {
esp.mValue = 4;
oper.mValue = 17;
}
else {
esp.mValue = 3;
oper.mValue = 11;
}
break;
case 't':
if (chaine.charAt(pos + 1) == 'h') {
esp.mValue = 2;
oper.mValue = 19;
}
else if ((chaine.charAt(pos + 1) == 'a') && (chaine.charAt(pos + 2) == 'n')) {
if (chaine.charAt(pos + 3) == 'h') {
esp.mValue = 4;
oper.mValue = 19;
}
else {
esp.mValue = 3;
oper.mValue = 13;
}
}
else
oper.mValue = -10;
break;
default:
oper.mValue = -10;
break;
}
}
// ....................................................................................
// CopyPartialString
private static StringBuffer CopyPartialString(StringBuffer chaine, int debut, int fin) {
StringBuffer chartemp;
int a = fin - debut + 1;
chartemp = new StringBuffer(a + 1);
for (int i = 0; i < a; i++)
chartemp.append(chaine.charAt(debut + i));
return chartemp;
}
public static double getResultFromExpression(String expr, StringBuffer writeTree) {
StringBuffer input = new StringBuffer(expr);
JArithmeticInterpreter jai = null;
try {
jai = JArithmeticInterpreter.constructTree(input, input.length());
} catch (Exception ignored) {}
if (jai == null) throw new IllegalArgumentException("Le calcul passé en paramètre est invalide");
if (writeTree != null) {
writeTree.setLength(0);
jai.writeTree(writeTree);
}
return jai.computeTree();
}
public static double getResultFromExpression(String expr) {
return getResultFromExpression(expr, null);
}
public static void main(String[] args) {
StringBuffer b = new StringBuffer(0);
String disp_res = StringUtil.formatDouble(JArithmeticInterpreter.getResultFromExpression("1245.25*2", b));
System.out.println(disp_res);
System.out.println(b);
} // */
}

View File

@@ -1,48 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.Objects;
import java.util.function.Supplier;
/**
* Represents a lazy loaded value.
*
* The value will be computed using the Supplier provided in the
* constructor, only the first time the {@link #get()} method is
* called.
*
* @param <T> the type of the enclosed value.
*/
public class Lazy<T> implements Supplier<T> {
private T cachedValue;
private final Supplier<T> supplier;
private boolean cached = false;
public Lazy(Supplier<T> s) {
supplier = Objects.requireNonNull(s);
}
/**
* Get the wrapped value, from cache or from the provider if it is not yet cached.
*
* If the provider throws an exception, it will be redirected to the caller as is, and no value will be cached (the next call to this method will
* execute the supplier again).
*/
@Override
public synchronized T get() {
if (!cached) // check outside synchronized method to reduce useless synchronization if value is already cached
set(supplier.get());
return cachedValue;
}
public synchronized void reset() {
cached = false;
cachedValue = null;
}
public synchronized void set(T value) {
cachedValue = value;
cached = true;
}
}

View File

@@ -1,48 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.Objects;
import fr.pandacube.lib.core.util.ThrowableUtil.SupplierException;
/**
* Represents a lazy loaded value.
*
* The value will be computed using the Supplier provided in the
* constructor, only the first time the {@link #get()} method is
* called.
*
* @param <T> the type of the enclosed value.
*/
public class LazyOrException<T> {
private T cachedValue;
private final SupplierException<T> supplier;
private boolean cached = false;
public LazyOrException(SupplierException<T> s) {
supplier = Objects.requireNonNull(s);
}
/**
* Get the wrapped value, from cache or from the provider if it is not yet cached.
*
* If the provider throws an exception, it will be redirected to the caller as is, and no value will be cached (the next call to this method will
* execute the supplier again).
*/
public synchronized T get() throws Exception {
if (!cached) // check outside synchronized method to reduce useless synchronization if value is already cached
set(supplier.get());
return cachedValue;
}
public synchronized void reset() {
cached = false;
cachedValue = null;
}
public synchronized void set(T value) {
cachedValue = value;
cached = true;
}
}

View File

@@ -1,68 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.Objects;
import java.util.function.ToIntBiFunction;
public class LevenshteinDistance {
private final String initialList;
private final int elementAdditionScore;
private final int elementDeletionScore;
private final ToIntBiFunction<Character, Character> elementDistanceFunction;
private int[] prev, curr;
private int progress = 0;
public LevenshteinDistance(String initList, String finList, int addScore, int delScore, ToIntBiFunction<Character, Character> elemDistFn) {
initialList = initList == null ? "" : initList;
elementAdditionScore = addScore;
elementDeletionScore = delScore;
elementDistanceFunction = elemDistFn == null ? ((e1, e2) -> Objects.equals(e1, e2) ? 0 : 1) : elemDistFn;
prev = new int[initialList.length() + 1];
curr = new int[initialList.length() + 1];
for (int i = 0; i < curr.length; i++)
curr[i] = i * elementDeletionScore;
add(finList);
}
public LevenshteinDistance() {
this(null, null, 1, 1, null);
}
public LevenshteinDistance(String initList) {
this(initList, null, 1, 1, null);
}
public int getCurrentDistance() {
return curr[curr.length - 1];
}
public void add(String els) {
for (char el : els.toCharArray())
add(el);
}
public void add(char el) {
progress++;
// swap score arrays
int[] tmp = prev; prev = curr; curr = tmp;
curr[0] = progress * elementAdditionScore;
for (int i = 1; i < curr.length; i++) {
int S = prev[i - 1] + elementDistanceFunction.applyAsInt(initialList.charAt(i - 1), el);
int A = prev[i] + elementAdditionScore;
int D = curr[i - 1] + elementDeletionScore;
curr[i] = Math.min(S, Math.min(A, D));
}
}
}

View File

@@ -1,13 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.List;
public class ListUtil {
public static void addLongRangeToList(List<Long> list, long min, long max) {
for (long i = min; i <= max; i++) {
list.add(i);
}
}
}

View File

@@ -1,71 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Log {
private static Logger logger = Logger.getGlobal();
private static final AtomicBoolean logDebug = new AtomicBoolean(false);
public static void setDebug(boolean newVal) {
logDebug.set(newVal);
}
public static boolean isDebugEnabled() {
return logDebug.get();
}
public static Logger getLogger() {
return logger;
}
public static void setLogger(Logger l) {
logger = l;
}
public static void info(String message) {
logger.info(message);
}
public static void warning(String message, Throwable t) {
logger.log(Level.WARNING, message, t);
}
public static void warning(Throwable t) {
logger.log(Level.WARNING, "", t);
}
public static void warning(String message) {
logger.warning(message);
}
public static void severe(String message, Throwable t) {
logger.log(Level.SEVERE, message, t);
}
public static void severe(Throwable t) {
logger.log(Level.SEVERE, "", t);
}
public static void severe(String message) {
logger.severe(message);
}
public static void debug(String message, Throwable t) {
if (!logDebug.get()) return;
logger.log(Level.INFO, message, t);
}
public static void debug(Throwable t) {
if (!logDebug.get()) return;
logger.log(Level.INFO, "", t);
}
public static void debug(String message) {
if (!logDebug.get()) return;
logger.info(message);
}
}

View File

@@ -1,188 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.AbstractList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Function;
/**
* A Wrapper list that provides a mapped view of the backend list.
* Every modification of this list will modify the bakend list.
* For each time a value is accessed or modified, the appropriate
* setter or getter is used to convert the value between the source {@code S}
* and the visible {@code T} type.
* @param <S> the source (backend) type
* @param <T> the visible (mapped) type
*/
public class MappedListView<S, T> extends AbstractList<T> {
protected final List<S> backend;
private final Function<S, T> getter;
private final Function<T, S> setter;
/**
*
* @param backend the list backing this list
* @param getter the function converting the value from the backing list to the value of this list.
* It is used for every operation involving reading data from the backing list and comparing existing data.
* For instance, {@link #indexOf(Object)} iterate through the backing list, converting all the values
* with {@code getter} before comparing them with the parameter of {@link #indexOf(Object)}.
* before comparing
* @param setter used for modification of the data in the list (typically {@code add} and {@code set} methods)
*/
public MappedListView(List<S> backend, Function<S, T> getter, Function<T, S> setter) {
this.backend = backend;
this.getter = getter;
this.setter = setter;
}
@Override
public int size() {
return backend.size();
}
@Override
public T get(int index) {
return getter.apply(backend.get(index));
}
@Override
public T set(int index, T element) {
return getter.apply(backend.set(index, setter.apply(element)));
}
@Override
public boolean add(T element) {
return backend.add(setter.apply(element));
}
@Override
public void add(int index, T element) {
backend.add(index, setter.apply(element));
}
@Override
public T remove(int index) {
return getter.apply(backend.remove(index));
}
@Override
public void clear() {
backend.clear();
}
@Override
public List<T> subList(int fromIndex, int toIndex) {
return new MappedListView<>(backend.subList(fromIndex, toIndex), getter, setter);
}
@Override
protected void removeRange(int fromIndex, int toIndex) {
backend.subList(fromIndex, toIndex).clear();
}
@Override
public boolean equals(Object o) {
return backend.equals(o);
}
@Override
public int hashCode() {
return backend.hashCode();
}
@SuppressWarnings("unchecked")
@Override
public int indexOf(Object o) {
return backend.indexOf(setter.apply((T) o));
}
@SuppressWarnings("unchecked")
@Override
public int lastIndexOf(Object o) {
return backend.lastIndexOf(setter.apply((T) o));
}
@Override
public Iterator<T> iterator() {
return new Iterator<>() {
final Iterator<S> wrappedIt = backend.iterator();
@Override
public boolean hasNext() {
return wrappedIt.hasNext();
}
@Override
public T next() {
return getter.apply(wrappedIt.next());
}
@Override
public void remove() {
wrappedIt.remove();
}
};
}
@Override
public ListIterator<T> listIterator() {
return listIterator(0);
}
@Override
public ListIterator<T> listIterator(int index) {
return new ListIterator<>() {
final ListIterator<S> wrappedIt = backend.listIterator(index);
@Override
public boolean hasNext() {
return wrappedIt.hasNext();
}
@Override
public T next() {
return getter.apply(wrappedIt.next());
}
@Override
public boolean hasPrevious() {
return wrappedIt.hasPrevious();
}
@Override
public T previous() {
return getter.apply(wrappedIt.previous());
}
@Override
public int nextIndex() {
return wrappedIt.nextIndex();
}
@Override
public int previousIndex() {
return wrappedIt.previousIndex();
}
@Override
public void remove() {
wrappedIt.remove();
}
@Override
public void set(T w) {
wrappedIt.set(setter.apply(w));
}
@Override
public void add(T w) {
wrappedIt.add(setter.apply(w));
}
};
}
}

View File

@@ -1,74 +0,0 @@
package fr.pandacube.lib.core.util;
import java.text.DecimalFormat;
public class MemoryUtil {
public enum MemoryUnit {
B(1, 1, null),
KB(1024, 1000, "k"),
MB(1024 * 1024, 1000_000, "M"),
GB(1024 * 1024 * 1024, 1000_000_000, "G");
final long valueTrad;
final long valueSI;
final String unitMultiplier;
public long toUnitRound(long byteCount, boolean si) {
return byteCount / value(si);
}
public double toUnit(long byteCount, boolean si) {
return byteCount / (double)value(si);
}
public long value(boolean si) {
return si ? valueSI : valueTrad;
}
public String unit(boolean si) {
return unitMultiplier == null ? "o" : (unitMultiplier + (si ? "o" : "io"));
}
MemoryUnit(long vTrad, long vSI, String uMult) {
valueTrad = vTrad;
valueSI = vSI;
unitMultiplier = uMult;
}
}
private static final DecimalFormat format = new DecimalFormat("#####0.00");
public static String humanReadableSize(long octet, MemoryUnit roundTo, boolean si) {
boolean neg = octet < 0;
long size = Math.abs(octet);
MemoryUnit unit = roundTo;
for (int ui = MemoryUnit.values().length - 1; ui >= 0; ui--) {
MemoryUnit u = MemoryUnit.values()[ui];
if (u == roundTo)
break;
if (size < u.value(si))
continue;
unit = u;
break;
}
String dispValue;
if (unit == roundTo) {
dispValue = ""+unit.toUnitRound(size, si);
}
else {
dispValue = format.format(unit.toUnit(size, si));
}
return (neg ? "-" : "") + dispValue + unit.unit(si);
}
public static String humanReadableSize(long octet) {
return humanReadableSize(octet, MemoryUnit.B, false);
}
}

View File

@@ -1,230 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableList;
public enum MinecraftVersion {
v1_7_2_to_1_7_5(4, "1.7.2-1.7.5"),
v1_7_6_to_1_7_10(5, "1.7.6-1.7.10"),
v1_8(47, "1.8.x"),
v1_9(107, "1.9"),
v1_9_1(108, "1.9.1"),
v1_9_2(109, "1.9.2"),
v1_9_3_to_1_9_4(110, "1.9.3", "1.9.4"),
v1_10(210, "1.10.x"),
v1_11(315, "1.11"),
v1_11_1_to_1_11_2(316, "1.11.1", "1.11.2"),
v1_12(335, "1.12"),
v1_12_1(338, "1.12.1"),
v1_12_2(340, "1.12.2"),
v1_13(393, "1.13"),
v1_13_1(401, "1.13.1"),
v1_13_2(404, "1.13.2"),
v1_14(477, "1.14"),
v1_14_1(480, "1.14.1"),
v1_14_2(485, "1.14.2"),
v1_14_3(490, "1.14.3"),
v1_14_4(498, "1.14.4"),
v1_15(573, "1.15"),
v1_15_1(575, "1.15.1"),
v1_15_2(578, "1.15.2"),
v1_16(735, "1.16"),
v1_16_1(736, "1.16.1"),
v1_16_2(751, "1.16.2"),
v1_16_3(753, "1.16.3"),
v1_16_4_to_1_16_5(754, "1.16.4", "1.16.5"),
v1_17(755, "1.17"),
v1_17_1(756, "1.17.1"),
v1_18_to_1_18_1(757, "1.18", "1.18.1"),
v1_18_2(758, "1.18.2"),
v1_19(759, "1.19");
// IMPORTANT: don't forget to update the versionMergeDisplay value when adding a new version;
private static final Map<EnumSet<MinecraftVersion>, List<String>> versionMergeDisplay;
static {
versionMergeDisplay = new HashMap<>();
versionMergeDisplay.put(EnumSet.of(v1_7_2_to_1_7_5, v1_7_6_to_1_7_10),
ImmutableList.of("1.7.2-1.7.10"));
versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1, v1_9_2, v1_9_3_to_1_9_4),
ImmutableList.of("1.9.x"));
versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1, v1_9_2),
ImmutableList.of("1.9-1.9.2"));
versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1),
ImmutableList.of("1.9", "1.9.1"));
versionMergeDisplay.put(EnumSet.of(v1_9_1, v1_9_2, v1_9_3_to_1_9_4),
ImmutableList.of("1.9.1-1.9.4"));
versionMergeDisplay.put(EnumSet.of(v1_9_1, v1_9_2),
ImmutableList.of("1.9.1", "1.9.2"));
versionMergeDisplay.put(EnumSet.of(v1_9_2, v1_9_3_to_1_9_4),
ImmutableList.of("1.9.2-1.9.4"));
versionMergeDisplay.put(EnumSet.of(v1_11, v1_11_1_to_1_11_2),
ImmutableList.of("1.11.x"));
versionMergeDisplay.put(EnumSet.of(v1_12, v1_12_1, v1_12_2),
ImmutableList.of("1.12.x"));
versionMergeDisplay.put(EnumSet.of(v1_12, v1_12_1),
ImmutableList.of("1.12", "1.12.1"));
versionMergeDisplay.put(EnumSet.of(v1_12_1, v1_12_2),
ImmutableList.of("1.12.1", "1.12.2"));
versionMergeDisplay.put(EnumSet.of(v1_13, v1_13_1, v1_13_2),
ImmutableList.of("1.13.x"));
versionMergeDisplay.put(EnumSet.of(v1_13, v1_13_1),
ImmutableList.of("1.13", "1.13.1"));
versionMergeDisplay.put(EnumSet.of(v1_13_1, v1_13_2),
ImmutableList.of("1.13.1", "1.13.2"));
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2, v1_14_3, v1_14_4),
ImmutableList.of("1.14.x"));
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2, v1_14_3),
ImmutableList.of("1.14-1.14.3"));
versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2, v1_14_3, v1_14_4),
ImmutableList.of("1.14.1-1.14.4"));
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2),
ImmutableList.of("1.14-1.14.2"));
versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2, v1_14_3),
ImmutableList.of("1.14.1-1.14.3"));
versionMergeDisplay.put(EnumSet.of(v1_14_2, v1_14_3, v1_14_4),
ImmutableList.of("1.14.2-1.14.4"));
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1),
ImmutableList.of("1.14", "1.14.1"));
versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2),
ImmutableList.of("1.14.1", "1.14.2"));
versionMergeDisplay.put(EnumSet.of(v1_14_2, v1_14_3),
ImmutableList.of("1.14.2", "1.14.3"));
versionMergeDisplay.put(EnumSet.of(v1_14_3, v1_14_4),
ImmutableList.of("1.14.3", "1.14.4"));
versionMergeDisplay.put(EnumSet.of(v1_15, v1_15_1, v1_15_2),
ImmutableList.of("1.15.x"));
versionMergeDisplay.put(EnumSet.of(v1_15, v1_15_1),
ImmutableList.of("1.15", "1.15.1"));
versionMergeDisplay.put(EnumSet.of(v1_15_1, v1_15_2),
ImmutableList.of("1.15.1", "1.15.2"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2, v1_16_3, v1_16_4_to_1_16_5),
ImmutableList.of("1.16.x"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2, v1_16_3),
ImmutableList.of("1.16-1.16.3"));
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2, v1_16_3, v1_16_4_to_1_16_5),
ImmutableList.of("1.16.1-1.16.5"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2),
ImmutableList.of("1.16-1.16.2"));
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2, v1_16_3),
ImmutableList.of("1.16.1-1.16.3"));
versionMergeDisplay.put(EnumSet.of(v1_16_2, v1_16_3, v1_16_4_to_1_16_5),
ImmutableList.of("1.16.2-1.16.5"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1),
ImmutableList.of("1.16", "1.16.1"));
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2),
ImmutableList.of("1.16.1", "1.16.2"));
versionMergeDisplay.put(EnumSet.of(v1_16_2, v1_16_3),
ImmutableList.of("1.16.2", "1.16.3"));
versionMergeDisplay.put(EnumSet.of(v1_16_3, v1_16_4_to_1_16_5),
ImmutableList.of("1.16.3-1.16.5"));
versionMergeDisplay.put(EnumSet.of(v1_17, v1_17_1),
ImmutableList.of("1.17.x"));
versionMergeDisplay.put(EnumSet.of(v1_18_to_1_18_1, v1_18_2),
ImmutableList.of("1.18.x"));
}
public final int id;
public final List<String> versionDisplay;
MinecraftVersion(int v, String... d) {
id = v;
versionDisplay = Arrays.asList(d);
}
@Override
public String toString() {
return toStringAnd();
}
public String toStringAnd() {
return StringUtil.joinGrammatically(", ", " et ", versionDisplay);
}
public String toStringOr() {
return StringUtil.joinGrammatically(", ", " ou ", versionDisplay);
}
public static MinecraftVersion getVersion(int v) {
for (MinecraftVersion mcV : values())
if (mcV.id == v) return mcV;
return null;
}
public static String displayOptimizedListOfVersionsAnd(List<MinecraftVersion> versions) {
return StringUtil.joinGrammatically(", ", " et ", getVersionsDisplayList(versions));
}
public static String displayOptimizedListOfVersionsOr(List<MinecraftVersion> versions) {
return StringUtil.joinGrammatically(", ", " ou ", getVersionsDisplayList(versions));
}
public static List<String> getVersionsDisplayList(List<MinecraftVersion> vList) {
if (vList == null)
return new ArrayList<>();
Set<MinecraftVersion> vSet = EnumSet.copyOf(vList);
List<String> ret = new ArrayList<>();
for (int i = 0; i < values().length; i++) {
if (!vSet.contains(values()[i]))
continue;
EnumSet<MinecraftVersion> vSubSet = EnumSet.of(values()[i]);
while (i + 1 < values().length && vSet.contains(values()[i + 1])) {
i++;
vSubSet.add(values()[i]);
if (!versionMergeDisplay.containsKey(vSubSet)) {
vSubSet.remove(values()[i]);
i--;
break;
}
}
if (vSubSet.size() == 1) {
ret.addAll(values()[i].versionDisplay);
}
else {
ret.addAll(versionMergeDisplay.get(vSubSet));
}
}
return ret;
}
}

View File

@@ -1,93 +0,0 @@
package fr.pandacube.lib.core.util;
public class MinecraftWebUtil {
/**
Convert a legacy Minecraft color coded String into HTML Format.
*/
// TODO update to support RGB colors (Bungee and Essentials notation).
// See JavaScript implementation at https://www.pandacube.fr/assets/js/global.js
public static String fromMinecraftColorCodeToHTML(char code_prefix, String ligne)
{
String color_char = "0123456789abcdefr";
StringBuilder builder = new StringBuilder();
char currentColor = 'r';
boolean bold = false, italic = false, underlined = false, strikethrough = false;
for (int i=0; i<ligne.length(); i++) {
char c = ligne.charAt(i);
if (c == code_prefix && (i<ligne.length()-1)) {
i++;
c = ligne.charAt(i);
if (color_char.contains(String.valueOf(Character.toLowerCase(c)))) {
if (bold) {
builder.append("</span>");
bold = false;
}
if (italic) {
builder.append("</span>");
italic = false;
}
if (underlined) {
builder.append("</span>");
underlined = false;
}
if (strikethrough) {
builder.append("</span>");
strikethrough = false;
}
if (Character.toLowerCase(c) != currentColor) {
if (currentColor != 'r')
builder.append("</span>");
if (Character.toLowerCase(c) != 'r')
builder.append("<span class=\"c").append(Character.toUpperCase(c)).append("\">");
currentColor = Character.toLowerCase(c);
}
}
else if (Character.toLowerCase(c) == 'l') {
if (!bold) {
builder.append("<span class=\"cL\">");
bold = true;
}
}
else if (Character.toLowerCase(c) == 'm') {
if (!strikethrough) {
builder.append("<span class=\"cM\">");
strikethrough = true;
}
}
else if (Character.toLowerCase(c) == 'n') {
if (!underlined) {
builder.append("<span class=\"cN\">");
underlined = true;
}
}
else if (Character.toLowerCase(c) == 'o') {
if (!italic) {
builder.append("<span class=\"cO\">");
italic = true;
}
}
else {
builder.append(code_prefix + c);
}
}
else
builder.append(c);
}
return builder.toString();
}
}

View File

@@ -1,45 +0,0 @@
package fr.pandacube.lib.core.util;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.Scanner;
import java.util.UUID;
public class OfflineUUID {
public static UUID getFromNickName(String nickname) {
byte[] from_str = ("OfflinePlayer:" + nickname).getBytes(StandardCharsets.UTF_8);
return UUID.nameUUIDFromBytes(from_str);
}
public static UUID[] getFromNickNames(String[] nicknames) {
Objects.requireNonNull(nicknames);
UUID[] uuids = new UUID[nicknames.length];
for (int i = 0; i < nicknames.length; i++)
uuids[i] = getFromNickName(nicknames[i]);
return uuids;
}
public static void main(String[] args) {
if (args.length == 0) {
try (Scanner s = new Scanner(System.in)) {
for(;;) {
System.out.print("Please input a player name: ");
if (!s.hasNext())
break;
String line = s.nextLine();
System.out.println(getFromNickName(line));
}
}
}
else {
for (String arg : args)
System.out.println("" + arg + ":" + getFromNickName(arg));
}
}
}

View File

@@ -1,102 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.List;
import java.util.Random;
import java.util.Set;
public class RandomUtil {
public static final Random rand = new Random();
public static int nextIntBetween(int minInclu, int maxExclu) {
return rand.nextInt(maxExclu - minInclu) + minInclu;
}
public static double nextDoubleBetween(double minInclu, double maxExclu) {
return rand.nextDouble() * (maxExclu - minInclu) + minInclu;
}
public static <T> T arrayElement(T[] arr) {
return (arr == null || arr.length == 0) ? null : arr[rand.nextInt(arr.length)];
}
public static <T> T listElement(List<T> arr) {
return (arr == null || arr.isEmpty()) ? null : arr.get(rand.nextInt(arr.size()));
}
public static char stringChar(String arr) {
return (arr == null || arr.isEmpty()) ? '\0' : arr.charAt(rand.nextInt(arr.length()));
}
/**
* Returns a random value from a set.
*
* May not be optimized (Actually O(n) )
* @param set the Set from which to pick a random value
* @return a random value from the set
*/
public static <T> T setElement(Set<T> set) {
if (set.isEmpty())
throw new IllegalArgumentException("set is empty");
int retI = rand.nextInt(set.size()), i = 0;
for (T e : set) {
if (retI == i)
return e;
i++;
}
throw new RuntimeException("Should never go to this line of code");
}
/**
* Return a value between 0 and the number of parameter minus 1, using the provided frequencies.
*
* The probability of each value to be returned depends of the frequencies provided.
* @param f the frequencies of each entries
* @return the index of an entry, or -1 if it is unable to pick anything (all the frequencies are 0 or there is not provided frequency)
*/
public static int randomIndexOfFrequencies(double... f) {
if (f == null)
throw new IllegalArgumentException("f cannot be null");
int n = f.length;
double[] fSums = new double[n];
double sum = 0;
for (int i = 0; i < n; i++) {
if (f[i] < 0)
throw new IllegalArgumentException("f[" + i + "] cannot be negative.");
fSums[i] = (sum += f[i]);
}
double r = rand.nextDouble() * sum;
for (int i = 0; i < n; i++) {
if (fSums[i] > r)
return i;
}
return n - 1;
}
public static final String PASSWORD_CHARSET_LATIN_LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
public static final String PASSWORD_CHARSET_LATIN_UPPERCASE = PASSWORD_CHARSET_LATIN_LOWERCASE.toUpperCase();
public static final String PASSWORD_CHARSET_DIGIT = "0123456789";
public static final String PASSWORD_CHARSET_SPECIAL = "@#+*/-;:,.?!='()[]{}&";
public static final String PASSWORD_CHARSET_NO_ANBIGUITY = "abcdefghkmnpqrstwxyzACDEFGHKLMNPQRSTWXYZ2345679";
public static String randomPassword(int length) {
return randomPassword(length, PASSWORD_CHARSET_NO_ANBIGUITY);
}
public static String randomPassword(int length, String charset) {
char[] pw = new char[length];
for (int i = 0; i < length; i++) {
pw[i] = stringChar(charset);
}
return String.valueOf(pw);
}
}

View File

@@ -1,472 +0,0 @@
package fr.pandacube.lib.core.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
import sun.misc.Unsafe;
public class Reflect {
private static final Unsafe sunMiscUnsafeInstance;
static {
classCache = Collections.synchronizedMap(new HashMap<>());
try {
sunMiscUnsafeInstance = (Unsafe) ofClass(Unsafe.class).field("theUnsafe")
.getStaticValue();
} catch (Exception e) {
throw new RuntimeException("Cannot access to " + Unsafe.class + ".theUnsafe value.", e);
}
}
private static final Map<Class<?>, ReflectClass<?>> classCache;
@SuppressWarnings("unchecked")
public static <T> ReflectClass<T> ofClass(Class<T> clazz) {
return (ReflectClass<T>) classCache.computeIfAbsent(clazz, ReflectClass::new);
}
public static ReflectClass<?> ofClass(String className) throws ClassNotFoundException {
return ofClass(Class.forName(className));
}
public static ReflectClass<?> ofClassOfInstance(Object instance) {
if (instance == null)
throw new IllegalArgumentException("instance can't be null");
return ofClass(instance.getClass());
}
private record MethodIdentifier(String methodName, Class<?>[] parameters) {
private MethodIdentifier {
Objects.requireNonNull(methodName);
parameters = (parameters == null) ? new Class<?>[0] : parameters;
}
@Override
public boolean equals(Object other) {
return other instanceof MethodIdentifier o
&& o.methodName.equals(methodName)
&& Arrays.equals(o.parameters, parameters);
}
@Override
public int hashCode() {
return methodName.hashCode() ^ Arrays.hashCode(parameters);
}
}
private record ConstructorIdentifier(Class<?>[] parameters) {
private ConstructorIdentifier {
parameters = (parameters == null) ? new Class<?>[0] : parameters;
}
@Override
public boolean equals(Object other) {
return other instanceof ConstructorIdentifier o
&& Arrays.equals(o.parameters, parameters);
}
@Override
public int hashCode() {
return Arrays.hashCode(parameters);
}
}
public static class ReflectClass<T> {
private final Class<T> clazz;
private final Map<MethodIdentifier, ReflectMethod<T>> methodCache = Collections.synchronizedMap(new HashMap<>());
private final Map<ConstructorIdentifier, ReflectConstructor<T>> constructorCache = Collections.synchronizedMap(new HashMap<>());
private final Map<String, ReflectField<T>> fieldCache = Collections.synchronizedMap(new HashMap<>());
private ReflectClass(Class<T> clazz) {
this.clazz = clazz;
}
/**
* Returns the class wrapped by this ReflectClass instance.
*/
public Class<T> get() {
return clazz;
}
private ReflectMethod<T> method(MethodIdentifier key, boolean bypassFilter) throws NoSuchMethodException {
ReflectMethod<T> method = methodCache.get(key);
if (method == null) {
method = new ReflectMethod<>(this, key, bypassFilter);
methodCache.put(key, method);
}
return method;
}
public ReflectMethod<T> method(String name, Class<?>... paramTypes) throws NoSuchMethodException {
return method(new MethodIdentifier(name, paramTypes), false);
}
public ReflectMethod<T> filteredMethod(String name, Class<?>... paramTypes) throws NoSuchMethodException {
return method(new MethodIdentifier(name, paramTypes), true);
}
private ReflectConstructor<T> constructor(ConstructorIdentifier key, boolean bypassFilter) throws NoSuchMethodException {
ReflectConstructor<T> constructor = constructorCache.get(key);
if (constructor == null) {
constructor = new ReflectConstructor<>(this, key, bypassFilter);
constructorCache.put(key, constructor);
}
return constructor;
}
public ReflectConstructor<T> constructor(Class<?>... paramTypes) throws NoSuchMethodException {
return constructor(new ConstructorIdentifier(paramTypes), false);
}
public ReflectConstructor<T> filteredConstructor(Class<?>... paramTypes) throws NoSuchMethodException {
return constructor(new ConstructorIdentifier(paramTypes), true);
}
private ReflectField<T> field0(String name, boolean bypassFilter) throws NoSuchFieldException {
ReflectField<T> constructor = fieldCache.get(name);
if (constructor == null) {
constructor = new ReflectField<>(this, name, bypassFilter);
fieldCache.put(name, constructor);
}
return constructor;
}
public ReflectField<T> field(String name) throws NoSuchFieldException {
return field0(name, false);
}
public ReflectField<T> filteredField(String name) throws NoSuchFieldException {
return field0(name, true);
}
}
public static abstract class ReflectMember<T, ID, EL, EX extends ReflectiveOperationException> {
protected final ReflectClass<T> reflectClass;
protected final ID identifier;
protected final EL cached;
protected ReflectMember(ReflectClass<T> c, ID id, boolean bypassFilter) throws EX {
reflectClass = c;
identifier = id;
cached = (bypassFilter) ? fetchFiltered() : fetch();
}
protected EL fetch() throws EX {
// get element in current class
try {
EL el = fetchFromClass(reflectClass.clazz);
setAccessible(el);
return el;
} catch (ReflectiveOperationException e1) {
@SuppressWarnings("unchecked")
EX ex = (EX) e1;
// get parent class
Class<? super T> superClass = reflectClass.clazz.getSuperclass();
if (superClass == null)
throw ex;
// get element in parent class (will do recursion)
try {
EL el = fetchFromReflectClass(ofClass(superClass));
setAccessible(el);
return el;
} catch (ReflectiveOperationException e2) {
ex.addSuppressed(e2);
throw ex;
}
}
}
protected EL fetchFiltered() throws EX {
// get element in current class
try {
EL el = fetchFromClass(reflectClass.clazz);
setAccessible(el);
return el;
} catch (ReflectiveOperationException e1) {
@SuppressWarnings("unchecked")
EX ex = (EX) e1;
// trying to bypass filtered member
try {
@SuppressWarnings("unchecked")
EL[] elements = (EL[]) Reflect.ofClassOfInstance(reflectClass.clazz)
.method(internalMethodNameElementArray(), boolean.class)
.invoke(reflectClass.clazz, false);
for (EL element : elements) {
if (isEqualOurElement(element)) {
// the values in the elements array have to be copied
// (using special private methods in reflection api) before using it
Object reflectionFactoryOfClazz = Reflect.ofClassOfInstance(reflectClass.clazz)
.method("getReflectionFactory")
.invoke(reflectClass.clazz);
@SuppressWarnings("unchecked")
EL copiedElement = (EL) Reflect.ofClassOfInstance(reflectionFactoryOfClazz)
.method(internalMethodNameCopyElement(), element.getClass())
.invoke(reflectionFactoryOfClazz, element);
setAccessible(copiedElement);
return copiedElement;
}
}
} catch (ReflectiveOperationException e2) {
ex.addSuppressed(e2);
}
throw ex;
}
}
protected abstract EL fetchFromClass(Class<T> clazz) throws EX;
protected abstract EL fetchFromReflectClass(ReflectClass<?> rc) throws EX;
protected abstract boolean isEqualOurElement(EL el);
protected abstract String internalMethodNameElementArray();
protected abstract String internalMethodNameCopyElement();
protected abstract void setAccessible(EL el);
public EL get() {
return cached;
}
public abstract int getModifiers();
}
public static class ReflectField<T> extends ReflectMember<T, String, Field, NoSuchFieldException> {
/* package */ ReflectField(ReflectClass<T> c, String name, boolean bypassFilter) throws NoSuchFieldException {
super(c, name, bypassFilter);
}
@Override protected Field fetchFromClass(Class<T> clazz) throws NoSuchFieldException { return clazz.getDeclaredField(identifier); }
@Override protected Field fetchFromReflectClass(ReflectClass<?> rc) throws NoSuchFieldException { return rc.field(identifier).get(); }
@Override protected boolean isEqualOurElement(Field el) { return identifier.equals(el.getName()); }
@Override protected String internalMethodNameElementArray() { return "getDeclaredFields0"; }
@Override protected String internalMethodNameCopyElement() { return "copyField"; }
@Override protected void setAccessible(Field el) { el.setAccessible(true); }
public Object getValue(Object instance) throws ReflectiveOperationException {
return get().get(instance);
}
public Object getStaticValue() throws ReflectiveOperationException {
return getValue(null);
}
public void setValue(Object instance, Object value) throws ReflectiveOperationException {
Field f = get();
if (Modifier.isFinal(f.getModifiers())) {
// if the field is final, we have to do some unsafe stuff :/
if (sunMiscUnsafeInstance != null) { // Java >= 16
// set the value of the field, directly in the memory
if (Modifier.isStatic(f.getModifiers())) {
long offset = sunMiscUnsafeInstance.staticFieldOffset(f);
sunMiscUnsafeInstance.putObject(sunMiscUnsafeInstance.staticFieldBase(f), offset, value);
} else {
long offset = sunMiscUnsafeInstance.objectFieldOffset(f);
sunMiscUnsafeInstance.putObject(instance, offset, value);
}
} else { // Java < 16
// change the modifier in the Field instance so the method #set(instance, value) doesnt throw an exception
int modifiers = f.getModifiers();
if (Modifier.isFinal(modifiers)) {
ofClass(Field.class).field("modifiers").fetchFiltered().set(f, modifiers & ~Modifier.FINAL);
}
f.set(instance, value);
}
}
else { // not final value
f.set(instance, value);
}
}
public void setStaticValue(Object value) throws ReflectiveOperationException {
setValue(null, value);
}
@Override
public int getModifiers() {
return get().getModifiers();
}
}
public static class ReflectMethod<T> extends ReflectMember<T, MethodIdentifier, Method, NoSuchMethodException> {
/* package */ ReflectMethod(ReflectClass<T> c, MethodIdentifier methodId, boolean bypassFilter) throws NoSuchMethodException {
super(c, methodId, bypassFilter);
}
@Override protected Method fetchFromClass(Class<T> clazz) throws NoSuchMethodException { return clazz.getDeclaredMethod(identifier.methodName, identifier.parameters); }
@Override protected Method fetchFromReflectClass(ReflectClass<?> rc) throws NoSuchMethodException { return rc.method(identifier, false).get(); }
@Override protected boolean isEqualOurElement(Method el) { return identifier.methodName.equals(el.getName()) && Arrays.equals(identifier.parameters, el.getParameterTypes()); }
@Override protected String internalMethodNameElementArray() { return "getDeclaredMethods0"; }
@Override protected String internalMethodNameCopyElement() { return "copyMethod"; }
@Override protected void setAccessible(Method el) { el.setAccessible(true); }
public Object invoke(Object instance, Object... values) throws ReflectiveOperationException {
return get().invoke(instance, values);
}
public Object invokeStatic(Object... values) throws ReflectiveOperationException {
return invoke(null, values);
}
@Override
public int getModifiers() {
return get().getModifiers();
}
}
public static class ReflectConstructor<T> extends ReflectMember<T, ConstructorIdentifier, Constructor<T>, NoSuchMethodException> {
/* package */ ReflectConstructor(ReflectClass<T> c, ConstructorIdentifier constructorId, boolean bypassFilter) throws NoSuchMethodException {
super(c, constructorId, bypassFilter);
}
// Override since we don't want to recursively search for a constructor
@Override
protected Constructor<T> fetch() throws NoSuchMethodException {
Constructor<T> el = fetchFromClass(reflectClass.clazz);
setAccessible(el);
return el;
}
@Override protected Constructor<T> fetchFromClass(Class<T> clazz) throws NoSuchMethodException { return clazz.getDeclaredConstructor(identifier.parameters); }
@Override protected Constructor<T> fetchFromReflectClass(ReflectClass<?> rc) { throw new UnsupportedOperationException(); }
@Override protected boolean isEqualOurElement(Constructor<T> el) { return Arrays.equals(identifier.parameters, el.getParameterTypes()); }
@Override protected String internalMethodNameElementArray() { return "getDeclaredConstructors0"; }
@Override protected String internalMethodNameCopyElement() { return "copyConstructor"; }
@Override protected void setAccessible(Constructor<T> el) { el.setAccessible(true); }
public T instanciate(Object... values) throws ReflectiveOperationException {
return get().newInstance(values);
}
@Override
public int getModifiers() {
return get().getModifiers();
}
}
private static final Cache<Class<?>, List<Class<?>>> subClassesLists = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
public static <E> List<Class<? extends E>> getAllSubclasses(Class<E> clazz) {
try {
@SuppressWarnings("unchecked")
List<Class<? extends E>> classes = (List<Class<? extends E>>) (List<?>) subClassesLists.get(clazz, () -> {
try (ScanResult scanResult = new ClassGraph().enableClassInfo().ignoreClassVisibility().scan()) {
return scanResult.getSubclasses(clazz.getName()).loadClasses();
}
});
return classes;
} catch(ExecutionException e) {
Log.severe(e);
return new ArrayList<>();
}
}
}

View File

@@ -10,6 +10,8 @@ import java.util.Objects;
import com.google.gson.JsonSyntaxException;
import fr.pandacube.lib.util.Log;
public class ServerPropertyFile {
private final transient File file;

View File

@@ -1,44 +0,0 @@
package fr.pandacube.lib.core.util;
import java.util.Arrays;
import java.util.List;
public class StringUtil {
public static String formatDouble(double d) {
if (d == (long) d)
return String.format("%d", (long) d);
return String.valueOf(d);
}
/**
* @param s Chaine de caractère à parcourir
* @param c_match le caractère dont on doit retourner le nombre d'occurence
* @return nombre d'occurence de <b>c_match</b> dans <b>s</b>
*/
public static int char_count(CharSequence s, char c_match) {
char[] chars = s.toString().toCharArray();
int count = 0;
for (char c : chars)
if (c == c_match) count++;
return count;
}
public static String joinGrammatically(CharSequence sep1, CharSequence sepFinal, List<String> strings) {
int size = strings == null ? 0 : strings.size();
return size == 0 ? "" : size == 1 ? strings.get(0) : String.join(sep1, strings.subList(0, --size)) + sepFinal + strings.get(size);
}
public static String repeat(char base, int count) {
char[] chars = new char[count];
Arrays.fill(chars, base);
return new String(chars);
}
}

View File

@@ -1,151 +0,0 @@
package fr.pandacube.lib.core.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
public class ThrowableUtil {
public static String stacktraceToString(Throwable t) {
if (t == null) return null;
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
try (PrintStream ps = new PrintStream(os, false, StandardCharsets.UTF_8)) {
t.printStackTrace(ps);
ps.flush();
}
return os.toString(StandardCharsets.UTF_8);
} catch (IOException e) {
return null;
}
}
/**
* Wraps a {@link SupplierException} into a try catch.
* @param supp the {@link SupplierException} to run and get the value from.
* @return the value returned by the provided supplier.
* @throws RuntimeException if the provided {@link SupplierException} throws a checked exception.
*/
public static <T> T wrapEx(SupplierException<T> supp) {
try {
return supp.get();
} catch (Exception e) {
throw uncheck(e, false);
}
}
/**
* Wraps a {@link RunnableException} into a try catch.
* @param run the {@link RunnableException} to run.
* @throws RuntimeException if the provided {@link RunnableException} throws a checked exception.
*/
public static void wrapEx(RunnableException run) {
try {
run.run();
} catch (Exception e) {
throw uncheck(e, false);
}
}
/**
* Wraps a {@link SupplierException} into a try catch.
* @param supp the {@link SupplierException} to run and get the value from.
* @return the value returned by the provided supplier.
* @throws RuntimeException if the provided {@link SupplierException} throws a checked exception.
*/
public static <T> T wrapReflectEx(SupplierException<T> supp) {
try {
return supp.get();
} catch (Exception e) {
throw uncheck(e, true);
}
}
/**
* Wraps a {@link RunnableException} into a try catch.
* @param run the {@link RunnableException} to run.
* @throws RuntimeException if the provided {@link RunnableException} throws a checked exception.
*/
public static void wrapReflectEx(RunnableException run) {
try {
run.run();
} catch (Exception e) {
throw uncheck(e, true);
}
}
/**
* A supplier that can possibly throw a checked exception
*/
@FunctionalInterface
public interface SupplierException<T> {
T get() throws Exception;
}
/**
* A runnable that can possibly throw a checked exception
*/
@FunctionalInterface
public interface RunnableException {
void run() throws Exception;
}
private static RuntimeException uncheck(Throwable t, boolean convertReflectionExceptionToError) {
if (t instanceof Error er) {
throw er;
}
if (t instanceof RuntimeException re)
return re;
if (convertReflectionExceptionToError) {
Error er = null;
if (t instanceof ClassNotFoundException ce) {
er = new NoClassDefFoundError();
er.initCause(ce);
}
else if (t instanceof IllegalAccessException ce) {
er = new IllegalAccessError();
er.initCause(ce);
}
else if (t instanceof NoSuchFieldException ce) {
er = new NoSuchFieldError();
er.initCause(ce);
}
else if (t instanceof NoSuchMethodException ce) {
er = new NoSuchMethodError();
er.initCause(ce);
}
else if (t instanceof InstantiationException ce) {
er = new InstantiationError();
er.initCause(ce);
}
if (er != null)
throw er;
if (t instanceof InvocationTargetException ce) {
Throwable cause = ce.getCause();
return uncheck(cause, false);
}
}
return new RuntimeException(t);
}
}

View File

@@ -1,30 +0,0 @@
package fr.pandacube.lib.core.util;
public class Tick {
/**
* Returns the number of tick is the provided duration, in second
* @param seconds the duration in second
* @return the same duration as provided, but in Minecraft server ticks
*/
public static long sec(long seconds) {
return seconds * 20;
}
/**
* Returns the number of tick is the provided duration, in second
* @param minutes the duration in minutes
* @return the same duration as provided, but in Minecraft server ticks
*/
public static long min(long minutes) {
return minutes * 1200;
}
}

View File

@@ -1,377 +0,0 @@
package fr.pandacube.lib.core.util;
import fr.pandacube.lib.core.commands.SuggestionsSupplier;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class TimeUtil {
private static final DateTimeFormatter cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Locale.getDefault());
private static final DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Locale.getDefault());
private static final DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Locale.getDefault());
private static final DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Locale.getDefault());
private static final DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Locale.getDefault());
private static final DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Locale.getDefault());
private static final DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.getDefault());
private static final DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Locale.getDefault());
private static final DateTimeFormatter HFormatter = DateTimeFormatter.ofPattern("H'h'", Locale.getDefault());
public static String relativeDateFr(long displayTime, boolean showSeconds, boolean compactWords) {
return relativeDateFr(displayTime,
showSeconds ? RelativePrecision.SECONDS : RelativePrecision.MINUTES,
showSeconds ? DisplayPrecision.SECONDS : DisplayPrecision.MINUTES,
compactWords);
}
public static String relativeDateFr(long displayTime, RelativePrecision relPrecision, DisplayPrecision dispPrecision, boolean compactWords) {
long currentTime = System.currentTimeMillis();
LocalDateTime displayDateTime = toLocalDateTime(displayTime);
LocalDateTime currentDateTime = toLocalDateTime(currentTime);
long timeDiff = currentTime - displayTime;
long timeDiffSec = timeDiff / 1000;
if (timeDiffSec < -1) {
// in the future
if (relPrecision == RelativePrecision.SECONDS) {
if (timeDiffSec > -60)
return "dans " + (-timeDiffSec) + (compactWords ? "s" : " secondes");
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.MINUTES)) {
if (timeDiffSec > -60)
return compactWords ? "dans moins d1min" : "dans moins dune minute";
if (timeDiffSec > -60*2) // dans 2 min
return compactWords ? "dans 1min" : "dans une minute";
if (timeDiffSec > -3600) // dans moins d1h
return "dans " + (-timeDiffSec/60) + (compactWords ? "min" : " minutes");
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.HOURS)) {
if (timeDiffSec > -3600) // dans moins d1h
return compactWords ? "dans moins d1h" : "dans moins dune heure";
if (timeDiffSec > -3600*2) // dans moins de 2h
return compactWords ? "dans 1h" : "dans une heure";
if (timeDiffSec > -3600*12) // dans moins de 12h
return "dans " + (-timeDiffSec/3600) + (compactWords ? "h" : " heures");
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.DAYS)) {
LocalDateTime nextMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0).plusDays(1);
if (displayDateTime.isBefore(nextMidnight)) // aujourd'hui
return "aujourdhui à " + dayTimeFr(displayTime, dispPrecision);
if (displayDateTime.isBefore(nextMidnight.plusDays(1))) // demain
return "demain à " + dayTimeFr(displayTime, dispPrecision);
if (displayDateTime.isBefore(nextMidnight.plusDays(5))) // dans moins d'1 semaine
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " "
+ dayOfMonthFormatter.format(displayDateTime) + " à "
+ dayTimeFr(displayTime, dispPrecision);
}
}
else {
// present and past
if (timeDiffSec <= 1)
return "maintenant";
if (relPrecision == RelativePrecision.SECONDS) {
if (timeDiffSec < 60) // ya moins d'1 min
return "il y a " + timeDiffSec + (compactWords ? "s" : " secondes");
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.MINUTES)) {
if (timeDiffSec < 60) // ya moins d'1 min
return compactWords ? "il y a moins d1min" : "il y a moins dune minute";
if (timeDiffSec < 60*2) // ya moins de 2 min
return compactWords ? "il y a 1min" : "il y a une minute";
if (timeDiffSec < 3600) // ya moins d'1h
return "il y a " + (timeDiffSec/60) + (compactWords ? "min" : " minutes");
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.HOURS)) {
if (timeDiffSec < 3600) // ya moins d'1h
return "il y a moins dune heure";
if (timeDiffSec < 3600*2) // ya moins de 2h
return "il y a une heure";
if (timeDiffSec < 3600*12) // ya moins de 12h
return "il y a " + (timeDiffSec/3600) + " heures";
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.DAYS)) {
LocalDateTime lastMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0);
if (!displayDateTime.isBefore(lastMidnight)) // aujourd'hui
return "aujourdhui à " + dayTimeFr(displayTime, dispPrecision);
if (!displayDateTime.isBefore(lastMidnight.minusDays(1))) // hier
return "hier à " + dayTimeFr(displayTime, dispPrecision);
if (!displayDateTime.isBefore(lastMidnight.minusDays(6))) // ya moins d'1 semaine
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " dernier à "
+ dayTimeFr(displayTime, dispPrecision);
}
}
return fullDateFr(displayTime, dispPrecision, true, compactWords);
}
public enum RelativePrecision {
NONE, DAYS, HOURS, MINUTES, SECONDS;
public boolean morePreciseThan(RelativePrecision o) { return ordinal() > o.ordinal(); }
public boolean lessPreciseThan(RelativePrecision o) { return ordinal() < o.ordinal(); }
public boolean morePreciseOrEqTo(RelativePrecision o) { return ordinal() >= o.ordinal(); }
public boolean lessPreciseOrEqTo(RelativePrecision o) { return ordinal() <= o.ordinal(); }
}
public enum DisplayPrecision {
DAYS, HOURS, MINUTES, SECONDS
}
public static String fullDateFr(long displayTime, boolean showSeconds, boolean showWeekday, boolean compactWords) {
return fullDateFr(displayTime, showSeconds ? DisplayPrecision.SECONDS : DisplayPrecision.MINUTES, showWeekday, compactWords);
}
public static String fullDateFr(long displayTime, DisplayPrecision precision, boolean showWeekday, boolean compactWords) {
LocalDateTime displayDateTime = toLocalDateTime(displayTime);
String ret = (showWeekday ? ((compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " ") : "")
+ dayOfMonthFormatter.format(displayDateTime) + " "
+ (compactWords ? cmpMonthFormatter : monthFormatter).format(displayDateTime) + " "
+ yearFormatter.format(displayDateTime);
if (precision == DisplayPrecision.DAYS)
return ret;
return ret + " à " + dayTimeFr(displayTime, precision);
}
public static String dayTimeFr(long displayTime, DisplayPrecision precision) {
DateTimeFormatter tFormatter = switch(precision) {
case HOURS -> HFormatter;
case MINUTES -> HMFormatter;
case SECONDS -> HMSFormatter;
default -> throw new IllegalArgumentException("precision");
};
return tFormatter.format(toLocalDateTime(displayTime));
}
private static LocalDateTime toLocalDateTime(long msTime) {
return Instant.ofEpochMilli(msTime).atZone(TimeZone.getDefault().toZoneId()).toLocalDateTime();
}
public static String durationToLongString(long msDuration, TimeUnit hUnit, TimeUnit lUnit, boolean spaces, boolean fr, boolean leadingZeros) {
if (lUnit.compareTo(hUnit) > 0) {
TimeUnit tmp = lUnit;
lUnit = hUnit;
hUnit = tmp;
}
if (lUnit.compareTo(TimeUnit.MILLISECONDS) < 0)
lUnit = TimeUnit.MILLISECONDS;
if (hUnit.compareTo(TimeUnit.MILLISECONDS) < 0)
hUnit = TimeUnit.MILLISECONDS;
AtomicLong remainingTime = new AtomicLong(msDuration);
AtomicBoolean oneDisplayed = new AtomicBoolean(false);
final TimeUnit fLUnit = lUnit, fHUnit = hUnit;
String ret = Arrays.stream(TimeUnit.values())
.sequential()
.filter(u -> u.compareTo(fLUnit) >= 0 && u.compareTo(fHUnit) <= 0)
.sorted(Comparator.reverseOrder())
.filter(u -> {
if (u.convert(remainingTime.get(), TimeUnit.MILLISECONDS) == 0 && !oneDisplayed.get())
return false;
oneDisplayed.set(true);
return true;
})
.map(u -> {
long v = u.convert(remainingTime.get(), TimeUnit.MILLISECONDS);
remainingTime.addAndGet(TimeUnit.MILLISECONDS.convert(-v, u));
return toString(v, leadingZeros ? timeUnitToLeftPadLength(u) : 1) + timeUnitToSuffix(u, fr);
})
.collect(Collectors.joining(spaces ? " " : ""));
// ensure there is at least something to display (for instance : "0s")
return oneDisplayed.get() ? ret : (toString(0, leadingZeros ? timeUnitToLeftPadLength(lUnit) : 1) + timeUnitToSuffix(lUnit, fr));
}
public static String timeUnitToSuffix(TimeUnit u, boolean fr) {
return switch (u) {
case DAYS -> fr ? "j" : "d";
case HOURS -> "h";
case MINUTES -> "m";
case SECONDS -> "s";
case MILLISECONDS -> "ms";
case MICROSECONDS -> "μs";
case NANOSECONDS -> "ns";
};
}
public static int timeUnitToLeftPadLength(TimeUnit u) {
return switch (u) {
case NANOSECONDS, MICROSECONDS, MILLISECONDS -> 3;
case SECONDS, MINUTES, HOURS -> 2;
case DAYS -> 1;
};
}
public static String toString(long value, int leftPad) {
String valueStr = Long.toString(value);
int padding = leftPad - valueStr.length();
if (padding <= 0)
return valueStr;
return "0".repeat(padding) + valueStr;
}
/**
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false)}
* @param msDuration the duration in ms
* @param milliseconds if the milliseconds are displayed or not
*/
public static String durationToString(long msDuration, boolean milliseconds) {
return durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false);
}
/**
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, true, true, false)}
* @param msDuration the duration in ms
*/
public static String durationToString(long msDuration) {
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, true, true, false);
}
/**
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, false, false, false)}
* @param msDuration the duration in ms
*/
public static String durationToParsableString(long msDuration) {
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, false, false, false);
}
/**
* @see <a href="https://github.com/EssentialsX/Essentials/blob/2.x/Essentials/src/main/java/com/earth2me/essentials/utils/DateUtil.java">Essentials DateUtil#parseDuration(String, boolean)</a>
*/
public static long parseDuration(String time, boolean future) throws Exception {
@SuppressWarnings("RegExpSimplifiable")
Pattern timePattern = Pattern.compile("(?:([0-9]+)\\s*y[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*mo[a-z]*[,\\s]*)?"
+ "(?:([0-9]+)\\s*w[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*d[a-z]*[,\\s]*)?"
+ "(?:([0-9]+)\\s*h[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*m[a-z]*[,\\s]*)?"
+ "(?:([0-9]+)\\s*(?:s[a-z]*)?)?", Pattern.CASE_INSENSITIVE);
Matcher m = timePattern.matcher(time);
int years = 0;
int months = 0;
int weeks = 0;
int days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
boolean found = false;
while (m.find()) {
if (m.group() == null || m.group().isEmpty()) continue;
for (int i = 0; i < m.groupCount(); i++)
if (m.group(i) != null && !m.group(i).isEmpty()) {
found = true;
break;
}
if (found) {
if (m.group(1) != null && !m.group(1).isEmpty()) years = Integer.parseInt(m.group(1));
if (m.group(2) != null && !m.group(2).isEmpty()) months = Integer.parseInt(m.group(2));
if (m.group(3) != null && !m.group(3).isEmpty()) weeks = Integer.parseInt(m.group(3));
if (m.group(4) != null && !m.group(4).isEmpty()) days = Integer.parseInt(m.group(4));
if (m.group(5) != null && !m.group(5).isEmpty()) hours = Integer.parseInt(m.group(5));
if (m.group(6) != null && !m.group(6).isEmpty()) minutes = Integer.parseInt(m.group(6));
if (m.group(7) != null && !m.group(7).isEmpty()) seconds = Integer.parseInt(m.group(7));
break;
}
}
if (!found) throw new Exception("Format de durée invalide");
Calendar c = new GregorianCalendar();
if (years > 0) c.add(Calendar.YEAR, years * (future ? 1 : -1));
if (months > 0) c.add(Calendar.MONTH, months * (future ? 1 : -1));
if (weeks > 0) c.add(Calendar.WEEK_OF_YEAR, weeks * (future ? 1 : -1));
if (days > 0) c.add(Calendar.DAY_OF_MONTH, days * (future ? 1 : -1));
if (hours > 0) c.add(Calendar.HOUR_OF_DAY, hours * (future ? 1 : -1));
if (minutes > 0) c.add(Calendar.MINUTE, minutes * (future ? 1 : -1));
if (seconds > 0) c.add(Calendar.SECOND, seconds * (future ? 1 : -1));
Calendar max = new GregorianCalendar();
max.add(Calendar.YEAR, 10);
if (c.after(max)) return max.getTimeInMillis();
return c.getTimeInMillis();
}
public static <S> SuggestionsSupplier<S> suggestDuration() {
return (s, ti, token, args) -> {
if (token.isEmpty()) {
return emptyTokenSuggestions;
}
List<String> remainingSuffixes = new ArrayList<>(allSuffixes);
char[] tokenChars = token.toCharArray();
String accSuffix = "";
for (char c : tokenChars) {
if (Character.isDigit(c)) {
scanAndRemovePastSuffixes(remainingSuffixes, accSuffix);
accSuffix = "";
} else if (Character.isLetter(c)) {
accSuffix += c;
} else
return Collections.emptyList();
}
String prefixToken = token.substring(0, token.length() - accSuffix.length());
return SuggestionsSupplier.collectFilteredStream(remainingSuffixes.stream(), accSuffix)
.stream()
.map(str -> prefixToken + str)
.collect(Collectors.toList());
};
}
private static final List<String> allSuffixes = Arrays.asList("y", "mo", "w", "d", "h", "m", "s");
private static final List<String> emptyTokenSuggestions = allSuffixes.stream().map(p -> "1" + p).collect(Collectors.toList());
private static void scanAndRemovePastSuffixes(List<String> suffixes, String foundSuffix) {
for (int i = 0; i < suffixes.size(); i++) {
if (foundSuffix.startsWith(suffixes.get(i))) {
suffixes.subList(0, i + 1).clear();
return;
}
}
}
}