Folder structure for PandaLib
This commit is contained in:
27
Core/.classpath
Normal file
27
Core/.classpath
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
2
Core/.gitignore
vendored
Normal file
2
Core/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target/
|
||||
dependency-reduced-pom.xml
|
23
Core/.project
Normal file
23
Core/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>PandacubeUtil</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
4
Core/.settings/org.eclipse.core.resources.prefs
Normal file
4
Core/.settings/org.eclipse.core.resources.prefs
Normal file
@@ -0,0 +1,4 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding//src/main/resources=UTF-8
|
||||
encoding/<project>=UTF-8
|
8
Core/.settings/org.eclipse.jdt.core.prefs
Normal file
8
Core/.settings/org.eclipse.jdt.core.prefs
Normal file
@@ -0,0 +1,8 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=11
|
4
Core/.settings/org.eclipse.m2e.core.prefs
Normal file
4
Core/.settings/org.eclipse.m2e.core.prefs
Normal file
@@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
69
Core/pom.xml
Normal file
69
Core/pom.xml
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>fr.pandacube.pandacube</groupId>
|
||||
<artifactId>PandacubeParentPublic</artifactId>
|
||||
<version>dev</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>PandacubeUtil</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>PandacubeUtil</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.bungeecord</groupId>
|
||||
<artifactId>bungeecord-chat</artifactId>
|
||||
<version>${bungeecord.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.javatuples</groupId>
|
||||
<artifactId>javatuples</artifactId>
|
||||
<version>1.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.classgraph</groupId>
|
||||
<artifactId>classgraph</artifactId>
|
||||
<version>4.8.90</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>
|
97
Core/src/main/java/fr/pandacube/Pandacube.java
Normal file
97
Core/src/main/java/fr/pandacube/Pandacube.java
Normal file
@@ -0,0 +1,97 @@
|
||||
package fr.pandacube;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import fr.pandacube.util.text_display.Chat;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
public class Pandacube {
|
||||
|
||||
public static final Locale LOCALE = Locale.FRANCE;
|
||||
|
||||
public static final TimeZone TIMEZONE = TimeZone.getTimeZone("Europe/Paris");
|
||||
|
||||
public static final Charset NETWORK_CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
public static final int NETWORK_TCP_BUFFER_SIZE = 1024 * 1024;
|
||||
|
||||
public static final int NETWORK_TIMEOUT = 0; // no timeout (milli-seconds)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//public static final ChatColor CHAT_GREEN_1_NORMAL = ChatColor.of("#5f9765"); // h=126 s=23 l=48
|
||||
|
||||
public static final ChatColor CHAT_GREEN_1_NORMAL = ChatColor.of("#3db849"); // h=126 s=50 l=48
|
||||
public static final ChatColor CHAT_GREEN_2 = ChatColor.of("#5ec969"); // h=126 s=50 l=58
|
||||
public static final ChatColor CHAT_GREEN_3 = ChatColor.of("#85d68d"); // h=126 s=50 l=68
|
||||
public static final ChatColor CHAT_GREEN_4 = ChatColor.of("#abe3b0"); // h=126 s=50 l=78
|
||||
|
||||
public static final ChatColor CHAT_GREEN_SATMAX = ChatColor.of("#00ff19"); // h=126 s=100 l=50
|
||||
public static final ChatColor CHAT_GREEN_1_SAT = ChatColor.of("#20d532"); // h=126 s=50 l=48
|
||||
public static final ChatColor CHAT_GREEN_2_SAT = ChatColor.of("#45e354"); // h=126 s=50 l=58
|
||||
public static final ChatColor CHAT_GREEN_3_SAT = ChatColor.of("#71ea7d"); // h=126 s=50 l=68
|
||||
public static final ChatColor CHAT_GREEN_4_SAT = ChatColor.of("#9df0a6"); // h=126 s=50 l=78
|
||||
|
||||
public static final ChatColor CHAT_BROWN_1 = ChatColor.of("#b26d3a"); // h=26 s=51 l=46
|
||||
public static final ChatColor CHAT_BROWN_2 = ChatColor.of("#cd9265"); // h=26 s=51 l=60
|
||||
public static final ChatColor CHAT_BROWN_3 = ChatColor.of("#e0bb9f"); // h=26 s=51 l=75
|
||||
|
||||
public static final ChatColor CHAT_BROWN_1_SAT = ChatColor.of("#b35c19"); // h=26 s=75 l=40
|
||||
public static final ChatColor CHAT_BROWN_2_SAT = ChatColor.of("#e28136"); // h=26 s=51 l=55
|
||||
public static final ChatColor CHAT_BROWN_3_SAT = ChatColor.of("#ecab79"); // h=26 s=51 l=70
|
||||
|
||||
public static final ChatColor CHAT_GRAY_MID = ChatColor.of("#888888");
|
||||
|
||||
public static final ChatColor CHAT_BROADCAST_COLOR = ChatColor.YELLOW;
|
||||
|
||||
|
||||
public static final ChatColor CHAT_DECORATION_COLOR = CHAT_GREEN_1_NORMAL;
|
||||
public static final char CHAT_DECORATION_CHAR = '-';
|
||||
public static final ChatColor CHAT_URL_COLOR = CHAT_GREEN_1_NORMAL;
|
||||
public static final ChatColor CHAT_COMMAND_COLOR = CHAT_GRAY_MID;
|
||||
public static final ChatColor CHAT_COMMAND_HIGHLIGHTED_COLOR = ChatColor.WHITE;
|
||||
public static final ChatColor CHAT_INFO_COLOR = CHAT_GREEN_4;
|
||||
public static final ChatColor CHAT_SUCCESS_COLOR = CHAT_GREEN_SATMAX;
|
||||
public static final ChatColor CHAT_FAILURE_COLOR = ChatColor.of("#ff3333");
|
||||
public static final ChatColor CHAT_DATA_COLOR = CHAT_GRAY_MID;
|
||||
|
||||
|
||||
public static final ChatColor CHAT_PM_PREFIX_DECORATION = Pandacube.CHAT_BROWN_2_SAT;
|
||||
public static final ChatColor CHAT_PM_SELF_MESSAGE = Pandacube.CHAT_GREEN_2;
|
||||
public static final ChatColor CHAT_PM_OTHER_MESSAGE = Pandacube.CHAT_GREEN_4;
|
||||
|
||||
|
||||
public static final Chat CHAT_MESSAGE_PREFIX() {
|
||||
return Chat.text("[")
|
||||
.color(CHAT_BROADCAST_COLOR)
|
||||
.thenDecoration("Pandacube")
|
||||
.thenText("] ");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Number of decoration character to put between the text and the border of
|
||||
* the line for left and right aligned text.
|
||||
*/
|
||||
public static final int CHAT_NB_CHAR_MARGIN = 1;
|
||||
|
||||
|
||||
|
||||
|
||||
static {
|
||||
// initialize class to avoid NCDFE when updating the plugin
|
||||
@SuppressWarnings({ "deprecation", "unused" })
|
||||
Class<?>
|
||||
c1 = fr.pandacube.util.network_api.server.RequestAnalyser.class,
|
||||
c2 = fr.pandacube.util.network_api.server.RequestAnalyser.BadRequestException.class,
|
||||
c3 = fr.pandacube.util.network_api.server.Response.class,
|
||||
c4 = fr.pandacube.util.text_display.ChatUtil.class;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
package fr.pandacube.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 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());
|
||||
}
|
||||
}
|
80
Core/src/main/java/fr/pandacube/util/BiMap.java
Normal file
80
Core/src/main/java/fr/pandacube/util/BiMap.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class BiMap<K, V> implements Iterable<Entry<K, V>> {
|
||||
|
||||
HashMap<K, V> map = new HashMap<>();
|
||||
HashMap<V, K> inversedMap = new HashMap<>();
|
||||
|
||||
public synchronized void put(K k, V v) {
|
||||
if (map.containsKey(k) || inversedMap.containsKey(v)) {
|
||||
map.remove(k);
|
||||
inversedMap.remove(v);
|
||||
}
|
||||
map.put(k, v);
|
||||
inversedMap.put(v, k);
|
||||
}
|
||||
|
||||
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 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();
|
||||
}
|
||||
|
||||
}
|
6
Core/src/main/java/fr/pandacube/util/Callback.java
Normal file
6
Core/src/main/java/fr/pandacube/util/Callback.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Callback<T> {
|
||||
public void done(T arg);
|
||||
}
|
15
Core/src/main/java/fr/pandacube/util/DirUtils.java
Normal file
15
Core/src/main/java/fr/pandacube/util/DirUtils.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class DirUtils {
|
||||
|
||||
|
||||
|
||||
public static void delete(final File target) {
|
||||
if (target.isDirectory())
|
||||
for (File child : target.listFiles())
|
||||
delete(child);
|
||||
target.delete();
|
||||
}
|
||||
}
|
90
Core/src/main/java/fr/pandacube/util/EnumUtil.java
Normal file
90
Core/src/main/java/fr/pandacube/util/EnumUtil.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
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) {
|
||||
T[] elements = enumType.getEnumConstants();
|
||||
|
||||
String out = "";
|
||||
boolean first = true;
|
||||
for (T el : elements) {
|
||||
if (!first) out += separator;
|
||||
first = false;
|
||||
out += el.name();
|
||||
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)];
|
||||
}
|
||||
|
||||
}
|
787
Core/src/main/java/fr/pandacube/util/GifDecoder.java
Normal file
787
Core/src/main/java/fr/pandacube/util/GifDecoder.java
Normal file
@@ -0,0 +1,787 @@
|
||||
package fr.pandacube.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;
|
||||
|
||||
static class GifFrame {
|
||||
public GifFrame(BufferedImage im, int del) {
|
||||
image = im;
|
||||
delay = del;
|
||||
}
|
||||
public BufferedImage image;
|
||||
public 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 = null;
|
||||
if (transparency) {
|
||||
c = new Color(0, 0, 0, 0); // assume background is transparent
|
||||
} else {
|
||||
c = new Color(lastBgColor); // use given background color
|
||||
}
|
||||
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 BufferedInputStream 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 e) {
|
||||
}
|
||||
} else {
|
||||
status = STATUS_OPEN_ERROR;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads GIF image from stream
|
||||
*
|
||||
* @param InputStream 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 e) {
|
||||
}
|
||||
} 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.indexOf("file:") >= 0) ||
|
||||
(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 = 0;
|
||||
while (n < blockSize) {
|
||||
count = in.read(block, n, blockSize - n);
|
||||
if (count == -1)
|
||||
break;
|
||||
n += count;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
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 e) {
|
||||
}
|
||||
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();
|
||||
String app = "";
|
||||
for (int i = 0; i < 11; i++) {
|
||||
app += (char) block[i];
|
||||
}
|
||||
if (app.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() {
|
||||
String id = "";
|
||||
for (int i = 0; i < 6; i++) {
|
||||
id += (char) read();
|
||||
}
|
||||
if (!id.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;
|
||||
/*int dispose = 0;
|
||||
boolean transparency = false;
|
||||
int delay = 0;*/
|
||||
lct = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips variable length blocks up to and including
|
||||
* next zero length block.
|
||||
*/
|
||||
protected void skip() {
|
||||
do {
|
||||
readBlock();
|
||||
} while ((blockSize > 0) && !err());
|
||||
}
|
||||
}
|
762
Core/src/main/java/fr/pandacube/util/JArithmeticInterpreter.java
Normal file
762
Core/src/main/java/fr/pandacube/util/JArithmeticInterpreter.java
Normal file
@@ -0,0 +1,762 @@
|
||||
package fr.pandacube.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 <20> palier l'absence de passage par variables ou reference
|
||||
|
||||
class VariableInt {
|
||||
public int mValue;
|
||||
}
|
||||
|
||||
// Classe principale
|
||||
|
||||
public class JArithmeticInterpreter {
|
||||
|
||||
// Variables
|
||||
|
||||
int mOperator;
|
||||
double mValue;
|
||||
JArithmeticInterpreter fg, fd;
|
||||
|
||||
// Methods
|
||||
|
||||
// ....................................................................................
|
||||
// Node
|
||||
|
||||
private JArithmeticInterpreter() {
|
||||
this(0, 0, null, null);
|
||||
}
|
||||
|
||||
// ....................................................................................
|
||||
// Node
|
||||
|
||||
private JArithmeticInterpreter(int Operator, double Value, JArithmeticInterpreter Fg, JArithmeticInterpreter Fd) {
|
||||
mOperator = Operator;
|
||||
mValue = Value;
|
||||
fg = Fg;
|
||||
fd = Fd;
|
||||
}
|
||||
|
||||
private JArithmeticInterpreter(int Operator, double Value) {
|
||||
this(Operator, Value, null, null);
|
||||
}
|
||||
|
||||
// ....................................................................................
|
||||
// Construct_Tree
|
||||
|
||||
private static JArithmeticInterpreter constructTree(StringBuffer string, int length, int error) {
|
||||
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) {
|
||||
error = 3;
|
||||
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 {
|
||||
error = 2; // symbole non reconnu
|
||||
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)) {
|
||||
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) && (priorite >= 1)) 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:
|
||||
error = 2; // symbole non reconnu
|
||||
return null;
|
||||
}
|
||||
|
||||
if (imbric != 0) {
|
||||
error = 1; // erreur de "parenthesage"
|
||||
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
|
||||
error = 3;
|
||||
return null;
|
||||
}
|
||||
StringBuffer temp = CopyPartialString(string, (position + 1), (length - 1 - Bimbric));
|
||||
node.fd = constructTree(temp, (length - position - 1 - Bimbric), error);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
else if (priorite == 5) {
|
||||
node = new JArithmeticInterpreter(0, calc_const(string, positionv), null, null);
|
||||
|
||||
return node;
|
||||
}
|
||||
else if (ope > 5) {
|
||||
node = new JArithmeticInterpreter(ope, 0, null, null);
|
||||
|
||||
if ((length - position - espa - Bimbric) == 0) { // argument absent
|
||||
error = 3;
|
||||
return null;
|
||||
}
|
||||
StringBuffer temp = CopyPartialString(string, (position + espa), (length - 1));
|
||||
node.fg = constructTree(temp, (length - position - espa - Bimbric), error);
|
||||
return node;
|
||||
}
|
||||
else {
|
||||
node = new JArithmeticInterpreter(ope, 0, null, null);
|
||||
|
||||
if ((position - Bimbric) == 0) { // argument absent
|
||||
error = 3;
|
||||
return null;
|
||||
}
|
||||
StringBuffer temp = CopyPartialString(string, Bimbric, (position - 1));
|
||||
node.fg = constructTree(temp, (position - Bimbric), error);
|
||||
if ((length - position - 1 - Bimbric) == 0) { // argument absent
|
||||
error = 3;
|
||||
return null;
|
||||
}
|
||||
temp = CopyPartialString(string, (position + 1), (length - 1 - Bimbric));
|
||||
node.fd = constructTree(temp, (length - position - 1 - Bimbric), error);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
// ....................................................................................
|
||||
|
||||
private double computeTree() {
|
||||
if (mOperator == 0) return mValue;
|
||||
int error = 0;
|
||||
|
||||
double valueL = fg.computeTree();
|
||||
|
||||
if (error != 0) return 0;
|
||||
double valueR = 0;
|
||||
|
||||
if (fd != null) valueR = fd.computeTree();
|
||||
if (error != 0) return 0;
|
||||
|
||||
switch (mOperator) {
|
||||
case 1: // +
|
||||
return (valueL + valueR);
|
||||
case 2: // -
|
||||
return (valueL - valueR);
|
||||
case 3: // *
|
||||
return (valueL * valueR);
|
||||
case 4: // -
|
||||
if (valueR == 0) {
|
||||
error = 1;
|
||||
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) {
|
||||
if (valueL < 0) error = 2;
|
||||
else
|
||||
error = 1;
|
||||
return 0;
|
||||
}
|
||||
return (Math.log(valueL) / Math.log(2));
|
||||
case 8: // log
|
||||
if (valueL <= 0) {
|
||||
if (valueL < 0) error = 2;
|
||||
else
|
||||
error = 1;
|
||||
return 0;
|
||||
}
|
||||
return Math.log(valueL);
|
||||
case 9: // sqrt
|
||||
if (valueL < 0) {
|
||||
error = 2;
|
||||
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)) ;
|
||||
else
|
||||
fg.writeTree(string);
|
||||
string.append('-');
|
||||
if ((fd.mOperator == 1) || (fd.mOperator == 2)) {
|
||||
parenthese = true;
|
||||
string.append('(');
|
||||
}
|
||||
fd.writeTree(string);
|
||||
if (parenthese == true) string.append(')');
|
||||
break;
|
||||
case 3:
|
||||
if ((fg.mOperator == 1) || (fg.mOperator == 2)) {
|
||||
parenthese = true;
|
||||
string.append('(');
|
||||
}
|
||||
fg.writeTree(string);
|
||||
if (parenthese == true) string.append(')');
|
||||
parenthese = false;
|
||||
string.append('*');
|
||||
if ((fd.mOperator == 1) || (fd.mOperator == 2)) {
|
||||
parenthese = true;
|
||||
string.append('(');
|
||||
}
|
||||
fd.writeTree(string);
|
||||
if (parenthese == true) string.append(')');
|
||||
break;
|
||||
case 4:
|
||||
if ((fg.mOperator == 1) || (fg.mOperator == 2)) {
|
||||
parenthese = true;
|
||||
string.append('(');
|
||||
}
|
||||
fg.writeTree(string);
|
||||
if (parenthese == true) string.append(')');
|
||||
parenthese = false;
|
||||
string.append('/');
|
||||
if ((fd.mOperator > 0) && (fd.mOperator < 5)) {
|
||||
parenthese = true;
|
||||
string.append('(');
|
||||
}
|
||||
fd.writeTree(string);
|
||||
if (parenthese == true) string.append(')');
|
||||
break;
|
||||
case 5:
|
||||
if ((fg.mOperator > 0) && (fg.mOperator < 5)) {
|
||||
parenthese = true;
|
||||
string.append('(');
|
||||
}
|
||||
fg.writeTree(string);
|
||||
if (parenthese == true) string.append(')');
|
||||
parenthese = false;
|
||||
string.append('^');
|
||||
if ((fd.mOperator > 0) && (fd.mOperator < 5)) {
|
||||
parenthese = true;
|
||||
string.append('(');
|
||||
}
|
||||
fd.writeTree(string);
|
||||
if (parenthese == true) 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(), 0);
|
||||
} catch (Exception e) {}
|
||||
|
||||
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);
|
||||
} // */
|
||||
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.ToIntBiFunction;
|
||||
|
||||
public class LevenshteinDistance {
|
||||
|
||||
private String initialList;
|
||||
|
||||
private int elementAdditionScore;
|
||||
private int elementDeletionScore;
|
||||
|
||||
private 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
13
Core/src/main/java/fr/pandacube/util/ListUtil.java
Normal file
13
Core/src/main/java/fr/pandacube/util/ListUtil.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package fr.pandacube.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);
|
||||
}
|
||||
}
|
||||
}
|
71
Core/src/main/java/fr/pandacube/util/Log.java
Normal file
71
Core/src/main/java/fr/pandacube/util/Log.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class Log {
|
||||
|
||||
private static Logger logger;
|
||||
private static AtomicBoolean logDebug = new AtomicBoolean(false);
|
||||
|
||||
public static void setDebugState(boolean newVal) {
|
||||
logDebug.set(newVal);
|
||||
}
|
||||
|
||||
public static boolean getDebugState() {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
53
Core/src/main/java/fr/pandacube/util/MappedListView.java
Normal file
53
Core/src/main/java/fr/pandacube/util/MappedListView.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class MappedListView<S, T> extends AbstractList<T> {
|
||||
|
||||
private 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 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<S, T>(backend.subList(fromIndex, toIndex), getter, setter);
|
||||
}
|
||||
@Override
|
||||
protected void removeRange(int fromIndex, int toIndex) {
|
||||
backend.subList(fromIndex, toIndex).clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
207
Core/src/main/java/fr/pandacube/util/MinecraftVersion.java
Normal file
207
Core/src/main/java/fr/pandacube/util/MinecraftVersion.java
Normal file
@@ -0,0 +1,207 @@
|
||||
package fr.pandacube.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");
|
||||
// IMPORTANT: don't forget to update the versionMergeDisplay value when adding a new version;
|
||||
|
||||
private static 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"));
|
||||
}
|
||||
|
||||
|
||||
public final int id;
|
||||
public final List<String> versionDisplay;
|
||||
|
||||
private 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 final 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
93
Core/src/main/java/fr/pandacube/util/MinecraftWebUtil.java
Normal file
93
Core/src/main/java/fr/pandacube/util/MinecraftWebUtil.java
Normal file
@@ -0,0 +1,93 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
public class MinecraftWebUtil {
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Convert a legacy Minecraft color coded String into HTML Format.
|
||||
|
||||
@param
|
||||
*/
|
||||
public static String fromMinecraftColorCodeToHTML(char code_prefix, String ligne)
|
||||
{
|
||||
String color_char = "0123456789abcdefr";
|
||||
|
||||
String builder = "";
|
||||
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(new String(new char[] {Character.toLowerCase(c)}))) {
|
||||
if (bold) {
|
||||
builder += "</span>";
|
||||
bold = false;
|
||||
}
|
||||
if (italic) {
|
||||
builder += "</span>";
|
||||
italic = false;
|
||||
}
|
||||
if (underlined) {
|
||||
builder += "</span>";
|
||||
underlined = false;
|
||||
}
|
||||
if (strikethrough) {
|
||||
builder += "</span>";
|
||||
strikethrough = false;
|
||||
}
|
||||
if (Character.toLowerCase(c) != currentColor) {
|
||||
if (currentColor != 'r')
|
||||
builder += "</span>";
|
||||
if (Character.toLowerCase(c) != 'r')
|
||||
builder += "<span class=\"c" + Character.toUpperCase(c) + "\">";
|
||||
currentColor = Character.toLowerCase(c);
|
||||
}
|
||||
|
||||
}
|
||||
else if (Character.toLowerCase(c) == 'l') {
|
||||
if (!bold) {
|
||||
builder += "<span class=\"cL\">";
|
||||
bold = true;
|
||||
}
|
||||
}
|
||||
else if (Character.toLowerCase(c) == 'm') {
|
||||
if (!strikethrough) {
|
||||
builder += "<span class=\"cM\">";
|
||||
strikethrough = true;
|
||||
}
|
||||
}
|
||||
else if (Character.toLowerCase(c) == 'n') {
|
||||
if (!underlined) {
|
||||
builder += "<span class=\"cN\">";
|
||||
underlined = true;
|
||||
}
|
||||
}
|
||||
else if (Character.toLowerCase(c) == 'o') {
|
||||
if (!italic) {
|
||||
builder += "<span class=\"cO\">";
|
||||
italic = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
builder += code_prefix + c;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
builder += c;
|
||||
}
|
||||
|
||||
return builder;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
28
Core/src/main/java/fr/pandacube/util/OfflineUUID.java
Normal file
28
Core/src/main/java/fr/pandacube/util/OfflineUUID.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.UUID;
|
||||
|
||||
public class OfflineUUID {
|
||||
public static void main(String[] args) {
|
||||
for (String arg : args)
|
||||
System.out.println("" + arg + ":" + getFromNickName(arg));
|
||||
if (args.length == 0)
|
||||
throw new IllegalArgumentException("no argument given. Please give at least one argument.");
|
||||
}
|
||||
|
||||
public static UUID getFromNickName(String nickname) {
|
||||
String str = "OfflinePlayer:" + nickname;
|
||||
byte[] from_str = str.getBytes(Charset.forName("UTF-8"));
|
||||
return UUID.nameUUIDFromBytes(from_str);
|
||||
}
|
||||
|
||||
public static UUID[] getFromNickNames(String[] nicknames) {
|
||||
if (nicknames == null) throw new NullPointerException();
|
||||
|
||||
UUID[] uuids = new UUID[nicknames.length];
|
||||
for (int i = 0; i < nicknames.length; i++)
|
||||
uuids[i] = getFromNickName(nicknames[i]);
|
||||
return uuids;
|
||||
}
|
||||
}
|
46
Core/src/main/java/fr/pandacube/util/RandomUtil.java
Normal file
46
Core/src/main/java/fr/pandacube/util/RandomUtil.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
public class RandomUtil {
|
||||
|
||||
public static 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()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random value from a set.
|
||||
*
|
||||
* May not be optimized (Actually O(n) )
|
||||
* @param arr
|
||||
* @return
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
185
Core/src/main/java/fr/pandacube/util/ReflexionUtil.java
Normal file
185
Core/src/main/java/fr/pandacube/util/ReflexionUtil.java
Normal file
@@ -0,0 +1,185 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
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;
|
||||
|
||||
public class ReflexionUtil {
|
||||
|
||||
public static Object getDeclaredFieldValue(Object instance, String fieldName) throws ReflectiveOperationException {
|
||||
if (instance == null)
|
||||
throw new IllegalArgumentException("instance can't be null");
|
||||
return getDeclaredFieldValue(instance.getClass(), instance, fieldName);
|
||||
}
|
||||
|
||||
public static Object getDeclaredFieldValue(String className, String fieldName) throws ReflectiveOperationException {
|
||||
return getDeclaredFieldValue(Class.forName(className), null, fieldName);
|
||||
}
|
||||
|
||||
public static Object getDeclaredFieldValue(String className, Object instance, String fieldName) throws ReflectiveOperationException {
|
||||
return getDeclaredFieldValue(Class.forName(className), instance, fieldName);
|
||||
}
|
||||
|
||||
public static Object getDeclaredFieldValue(Class<?> clazz, String fieldName) throws ReflectiveOperationException {
|
||||
return getDeclaredFieldValue(clazz, null, fieldName);
|
||||
}
|
||||
|
||||
public static Object getDeclaredFieldValue(Class<?> clazz, Object instance, String fieldName) throws ReflectiveOperationException {
|
||||
Field f = clazz.getDeclaredField(fieldName);
|
||||
f.setAccessible(true);
|
||||
return f.get(instance);
|
||||
}
|
||||
|
||||
|
||||
public static Object getFieldValue(Object instance, String fieldName) throws ReflectiveOperationException {
|
||||
if (instance == null)
|
||||
throw new IllegalArgumentException("instance can't be null");
|
||||
return getFieldValue(instance.getClass(), instance, fieldName);
|
||||
}
|
||||
|
||||
public static Object getFieldValue(String className, String fieldName) throws ReflectiveOperationException {
|
||||
return getFieldValue(Class.forName(className), null, fieldName);
|
||||
}
|
||||
|
||||
public static Object getFieldValue(String className, Object instance, String fieldName) throws ReflectiveOperationException {
|
||||
return getFieldValue(Class.forName(className), instance, fieldName);
|
||||
}
|
||||
|
||||
public static Object getFieldValue(Class<?> clazz, String fieldName) throws ReflectiveOperationException {
|
||||
return getFieldValue(clazz, null, fieldName);
|
||||
}
|
||||
|
||||
public static Object getFieldValue(Class<?> clazz, Object instance, String fieldName) throws ReflectiveOperationException {
|
||||
Field f = clazz.getField(fieldName);
|
||||
f.setAccessible(true);
|
||||
return f.get(instance);
|
||||
}
|
||||
|
||||
|
||||
public static Object invokeMethod(Object instance, String methodName) throws ReflectiveOperationException {
|
||||
if (instance == null)
|
||||
throw new IllegalArgumentException("instance can't be null");
|
||||
return invokeMethod(instance, methodName, new Class<?>[0]);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
|
||||
if (instance == null)
|
||||
throw new IllegalArgumentException("instance can't be null");
|
||||
return invokeMethod(instance.getClass(), instance, methodName, parameterTypes, args);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(String className, String methodName) throws ReflectiveOperationException {
|
||||
return invokeMethod(Class.forName(className), null, methodName, new Class<?>[0]);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(String className, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
|
||||
return invokeMethod(Class.forName(className), null, methodName, parameterTypes, args);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(String className, Object instance, String methodName) throws ReflectiveOperationException {
|
||||
return invokeMethod(Class.forName(className), instance, methodName, new Class<?>[0]);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(String className, Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
|
||||
return invokeMethod(Class.forName(className), instance, methodName, parameterTypes, args);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(Class<?> clazz, String methodName) throws ReflectiveOperationException {
|
||||
return invokeMethod(clazz, null, methodName, new Class<?>[0]);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
|
||||
return invokeMethod(clazz, null, methodName, parameterTypes, args);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(Class<?> clazz, Object instance, String methodName) throws ReflectiveOperationException {
|
||||
return invokeMethod(clazz, instance, methodName, new Class<?>[0]);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(Class<?> clazz, Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
|
||||
Method m = clazz.getMethod(methodName, parameterTypes);
|
||||
m.setAccessible(true);
|
||||
return m.invoke(instance, args);
|
||||
}
|
||||
|
||||
|
||||
public static Object invokeDeclaredMethod(Object instance, String methodName) throws ReflectiveOperationException {
|
||||
if (instance == null)
|
||||
throw new IllegalArgumentException("instance can't be null");
|
||||
return invokeDeclaredMethod(instance, methodName, new Class<?>[0]);
|
||||
}
|
||||
|
||||
public static Object invokeDeclaredMethod(Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
|
||||
if (instance == null)
|
||||
throw new IllegalArgumentException("instance can't be null");
|
||||
return invokeDeclaredMethod(instance.getClass(), instance, methodName, parameterTypes, args);
|
||||
}
|
||||
|
||||
public static Object invokeDeclaredMethod(String className, String methodName) throws ReflectiveOperationException {
|
||||
return invokeDeclaredMethod(Class.forName(className), null, methodName, new Class<?>[0]);
|
||||
}
|
||||
|
||||
public static Object invokeDeclaredMethod(String className, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
|
||||
return invokeDeclaredMethod(Class.forName(className), null, methodName, parameterTypes, args);
|
||||
}
|
||||
|
||||
public static Object invokeDeclaredMethod(String className, Object instance, String methodName) throws ReflectiveOperationException {
|
||||
return invokeDeclaredMethod(Class.forName(className), instance, methodName, new Class<?>[0]);
|
||||
}
|
||||
|
||||
public static Object invokeDeclaredMethod(String className, Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
|
||||
return invokeDeclaredMethod(Class.forName(className), instance, methodName, parameterTypes, args);
|
||||
}
|
||||
|
||||
public static Object invokeDeclaredMethod(Class<?> clazz, String methodName) throws ReflectiveOperationException {
|
||||
return invokeDeclaredMethod(clazz, null, methodName, new Class<?>[0]);
|
||||
}
|
||||
|
||||
public static Object invokeDeclaredMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
|
||||
return invokeDeclaredMethod(clazz, null, methodName, parameterTypes, args);
|
||||
}
|
||||
|
||||
public static Object invokeDeclaredMethod(Class<?> clazz, Object instance, String methodName) throws ReflectiveOperationException {
|
||||
return invokeDeclaredMethod(clazz, instance, methodName, new Class<?>[0]);
|
||||
}
|
||||
|
||||
public static Object invokeDeclaredMethod(Class<?> clazz, Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
|
||||
Method m = clazz.getDeclaredMethod(methodName, parameterTypes);
|
||||
m.setAccessible(true);
|
||||
return m.invoke(instance, args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static 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 null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
130
Core/src/main/java/fr/pandacube/util/ServerPropertyFile.java
Normal file
130
Core/src/main/java/fr/pandacube/util/ServerPropertyFile.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
public class ServerPropertyFile {
|
||||
|
||||
private transient File file;
|
||||
|
||||
private String name = "default_name";
|
||||
private String memory = "512M";
|
||||
private String javaArgs = "";
|
||||
private String MinecraftArgs = "";
|
||||
private String jarFile = "";
|
||||
private long startupDelay = 0;
|
||||
private boolean run = true;
|
||||
|
||||
public ServerPropertyFile(File f) {
|
||||
if (f == null) throw new IllegalArgumentException("f ne doit pas être null");
|
||||
file = f;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge le fichier de configuration dans cette instance de la classe
|
||||
*
|
||||
* @return true si le chargement a réussi, false sinon
|
||||
*/
|
||||
public boolean loadFromFile() {
|
||||
try (BufferedReader in = new BufferedReader(new FileReader(file))) {
|
||||
|
||||
ServerPropertyFile dataFile = new Gson().fromJson(in, getClass());
|
||||
|
||||
name = dataFile.name;
|
||||
memory = dataFile.memory;
|
||||
javaArgs = dataFile.javaArgs;
|
||||
MinecraftArgs = dataFile.MinecraftArgs;
|
||||
jarFile = dataFile.jarFile;
|
||||
run = dataFile.run;
|
||||
startupDelay = dataFile.startupDelay;
|
||||
|
||||
return true;
|
||||
} catch(JsonSyntaxException e) {
|
||||
Log.severe("Error in config file " + file + ": backed up and creating a new one from previous or default values.", e);
|
||||
return save();
|
||||
} catch (IOException e) {
|
||||
Log.severe(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean save() {
|
||||
try (BufferedWriter out = new BufferedWriter(new FileWriter(file, false))) {
|
||||
|
||||
new Gson().toJson(this, out);
|
||||
out.flush();
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.severe(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getMemory() {
|
||||
return memory;
|
||||
}
|
||||
|
||||
public String getJavaArgs() {
|
||||
return javaArgs;
|
||||
}
|
||||
|
||||
public String getMinecraftArgs() {
|
||||
return MinecraftArgs;
|
||||
}
|
||||
|
||||
public String getJarFile() {
|
||||
return jarFile;
|
||||
}
|
||||
|
||||
public boolean isRun() {
|
||||
return run;
|
||||
}
|
||||
|
||||
public long getStartupDelay() {
|
||||
return startupDelay;
|
||||
}
|
||||
|
||||
public void setName(String n) {
|
||||
if (n == null || !n.matches("^[a-zA-Z]$")) throw new IllegalArgumentException();
|
||||
name = n;
|
||||
}
|
||||
|
||||
public void setMemory(String m) {
|
||||
if (m == null || !m.matches("^[0-9]+[mgMG]$")) throw new IllegalArgumentException();
|
||||
memory = m;
|
||||
}
|
||||
|
||||
public void setJavaArgs(String ja) {
|
||||
if (ja == null) throw new IllegalArgumentException();
|
||||
javaArgs = ja;
|
||||
}
|
||||
|
||||
public void setMinecraftArgs(String ma) {
|
||||
if (ma == null) throw new IllegalArgumentException();
|
||||
MinecraftArgs = ma;
|
||||
}
|
||||
|
||||
public void setJarFile(String j) {
|
||||
if (j == null) throw new IllegalArgumentException();
|
||||
jarFile = j;
|
||||
}
|
||||
|
||||
public void setRun(boolean r) {
|
||||
run = r;
|
||||
}
|
||||
|
||||
}
|
55
Core/src/main/java/fr/pandacube/util/StringUtil.java
Normal file
55
Core/src/main/java/fr/pandacube/util/StringUtil.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
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(String base, int count) {
|
||||
int baseLength = base.length();
|
||||
char[] baseChars = base.toCharArray();
|
||||
char[] chars = new char[baseLength * count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
System.arraycopy(baseChars, 0, chars, i * baseLength, baseLength);
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
|
||||
public static String repeat(char base, int count) {
|
||||
char[] chars = new char[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
chars[i] = base;
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
}
|
23
Core/src/main/java/fr/pandacube/util/ThrowableUtil.java
Normal file
23
Core/src/main/java/fr/pandacube/util/ThrowableUtil.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
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, "UTF-8")) {
|
||||
t.printStackTrace(ps);
|
||||
ps.flush();
|
||||
}
|
||||
return os.toString("UTF-8");
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
218
Core/src/main/java/fr/pandacube/util/TypeConverter.java
Normal file
218
Core/src/main/java/fr/pandacube/util/TypeConverter.java
Normal file
@@ -0,0 +1,218 @@
|
||||
package fr.pandacube.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Utility for conversion of basic Java types and JsonElements types
|
||||
* @author Marc
|
||||
*
|
||||
*/
|
||||
public class TypeConverter {
|
||||
|
||||
|
||||
public static Integer toInteger(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (o instanceof JsonElement) {
|
||||
try {
|
||||
return ((JsonElement)o).getAsInt();
|
||||
} catch(UnsupportedOperationException e) {
|
||||
throw new ConvertionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (o instanceof Number) {
|
||||
return ((Number)o).intValue();
|
||||
}
|
||||
if (o instanceof String) {
|
||||
try {
|
||||
return Integer.parseInt((String)o);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConvertionException(e);
|
||||
}
|
||||
}
|
||||
if (o instanceof Boolean) {
|
||||
return ((Boolean)o) ? 1 : 0;
|
||||
}
|
||||
|
||||
throw new ConvertionException("No integer convertion available for an instance of "+o.getClass());
|
||||
}
|
||||
|
||||
public static int toPrimInt(Object o) {
|
||||
Integer val = toInteger(o);
|
||||
if (val == null)
|
||||
throw new ConvertionException("null values can't converted to primitive int");
|
||||
return val;
|
||||
}
|
||||
|
||||
public static Double toDouble(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (o instanceof JsonElement) {
|
||||
try {
|
||||
return ((JsonElement)o).getAsDouble();
|
||||
} catch(UnsupportedOperationException e) {
|
||||
throw new ConvertionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (o instanceof Number) {
|
||||
return ((Number)o).doubleValue();
|
||||
}
|
||||
if (o instanceof String) {
|
||||
try {
|
||||
return Double.parseDouble((String)o);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConvertionException(e);
|
||||
}
|
||||
}
|
||||
if (o instanceof Boolean) {
|
||||
return ((Boolean)o) ? 1d : 0d;
|
||||
}
|
||||
|
||||
throw new ConvertionException("No double convertion available for an instance of "+o.getClass());
|
||||
|
||||
}
|
||||
|
||||
public static double toPrimDouble(Object o) {
|
||||
Double val = toDouble(o);
|
||||
if (val == null)
|
||||
throw new ConvertionException("null values can't converted to primitive int");
|
||||
return val;
|
||||
}
|
||||
|
||||
public static String toString(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (o instanceof JsonElement) {
|
||||
try {
|
||||
return ((JsonElement)o).getAsString();
|
||||
} catch(UnsupportedOperationException e) {
|
||||
throw new ConvertionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (o instanceof Number || o instanceof String || o instanceof Boolean || o instanceof Character) {
|
||||
return o.toString();
|
||||
}
|
||||
|
||||
throw new ConvertionException("No string convertion available for an instance of "+o.getClass());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param o the object to convert to good type
|
||||
* @param mapIntKeys if the String key representing an int should be duplicated as integer type,
|
||||
* which map to the same value as the original String key. For example, if a key is "12" and map
|
||||
* to the object <i>o</i>, an integer key 12 will be added and map to the same object
|
||||
* <i>o</i>
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<Object, Object> toMap(Object o, boolean mapIntKeys) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (o instanceof JsonElement) {
|
||||
o = new Gson().fromJson((JsonElement)o, Object.class);
|
||||
}
|
||||
|
||||
if (o instanceof Map) {
|
||||
Map<Object, Object> currMap = (Map<Object, Object>) o;
|
||||
if (mapIntKeys) {
|
||||
Map<Integer, Object> newEntries = new HashMap<>();
|
||||
for (Map.Entry<Object, Object> entry : currMap.entrySet()) {
|
||||
if (entry.getKey() instanceof String) {
|
||||
try {
|
||||
int intKey = Integer.parseInt((String)entry.getKey());
|
||||
newEntries.put(intKey, entry.getValue());
|
||||
} catch (NumberFormatException e) { }
|
||||
}
|
||||
}
|
||||
if (!newEntries.isEmpty()) {
|
||||
currMap = new HashMap<>(currMap);
|
||||
currMap.putAll(newEntries);
|
||||
}
|
||||
}
|
||||
return currMap;
|
||||
}
|
||||
|
||||
if (o instanceof List) {
|
||||
List<?> list = (List<?>) o;
|
||||
Map<Object, Object> map = new HashMap<>();
|
||||
for(int i = 0; i < list.size(); i++) {
|
||||
map.put(Integer.toString(i), list.get(i));
|
||||
map.put(i, list.get(i));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
throw new ConvertionException("No Map convertion available for an instance of "+o.getClass());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<Object> toList(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (o instanceof JsonElement) {
|
||||
o = new Gson().fromJson((JsonElement)o, Object.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (o instanceof List) {
|
||||
return (List<Object>) o;
|
||||
}
|
||||
|
||||
if (o instanceof Map) {
|
||||
return new ArrayList<>(((Map<?, ?>)o).values());
|
||||
}
|
||||
|
||||
|
||||
throw new ConvertionException("No Map convertion available for an instance of "+o.getClass());
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class ConvertionException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ConvertionException(String m) {
|
||||
super(m);
|
||||
}
|
||||
public ConvertionException(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
public ConvertionException(String m, Throwable t) {
|
||||
super(m, t);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package fr.pandacube.util.commands;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import fr.pandacube.util.text_display.ChatStatic;
|
||||
|
||||
public class AbstractCommand extends ChatStatic {
|
||||
|
||||
public final String commandName;
|
||||
|
||||
public AbstractCommand(String cmdName) {
|
||||
commandName = cmdName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Concatène les chaines de caractères passés dans <code>args</code> (avec
|
||||
* <code>" "</code> comme séparateur), en ommettant
|
||||
* celles qui se trouvent avant <code>index</code>.<br/>
|
||||
* Par exemple :
|
||||
* </p>
|
||||
* <code>
|
||||
* getLastParams(new String[] {"test", "bouya", "chaka", "bukkit"}, 1);
|
||||
* </code>
|
||||
* <p>
|
||||
* retournera la chaine "bouya chaka bukkit"
|
||||
*
|
||||
* @param args liste des arguments d'une commandes.<br/>
|
||||
* Le premier élément est l'argument qui suit le nom de la commande.
|
||||
* Usuellement, ce paramètre correspond au paramètre
|
||||
* <code>args</code> de la méthode onCommand
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
public static String getLastParams(String[] args, int index) {
|
||||
if (index < 0 || index >= args.length) return null;
|
||||
return String.join(" ", Arrays.copyOfRange(args, index, args.length));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package fr.pandacube.util.commands;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Throw an instance of this exception to indicate to the plugin command handler
|
||||
* that the user has missused the command. The message, if provided, must indicate
|
||||
* the reason of the mussusage of the command. It will be displayed on the screen
|
||||
* with eventually indication of how to use the command (help command for example).
|
||||
* If a {@link Throwable} cause is provided, it will be relayed to the plugin {@link Logger}.
|
||||
*
|
||||
*/
|
||||
public class BadCommandUsage extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public BadCommandUsage() {
|
||||
super();
|
||||
}
|
||||
|
||||
public BadCommandUsage(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public BadCommandUsage(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BadCommandUsage(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@@ -0,0 +1,345 @@
|
||||
package fr.pandacube.util.commands;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import fr.pandacube.util.ListUtil;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SuggestionsSupplier<S> {
|
||||
|
||||
/**
|
||||
* Number of suggestion visible at once without having to scroll
|
||||
*/
|
||||
public static int VISIBLE_SUGGESTION_COUNT = 10;
|
||||
|
||||
|
||||
public abstract List<String> getSuggestions(S sender, int tokenIndex, String token, String[] args);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static Predicate<String> filter(String token) {
|
||||
return suggestion -> suggestion != null && suggestion.toLowerCase().startsWith(token.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the provided {@link Stream} of string according to the provided token, using the filter returned by {@link #filter(String)},
|
||||
* then returns the strings collected into a {@link List}.
|
||||
*
|
||||
* This methods consume the provided stream, so will not be usable anymore.
|
||||
*/
|
||||
public static List<String> collectFilteredStream(Stream<String> stream, String token) {
|
||||
return stream.filter(filter(token)).sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static <S> SuggestionsSupplier<S> empty() { return (s, ti, t, a) -> Collections.emptyList(); }
|
||||
|
||||
public static <S> SuggestionsSupplier<S> fromCollectionsSupplier(Supplier<Collection<String>> streamSupplier) {
|
||||
return (s, ti, token, a) -> collectFilteredStream(streamSupplier.get().stream(), token);
|
||||
}
|
||||
|
||||
public static <S> SuggestionsSupplier<S> fromStreamSupplier(Supplier<Stream<String>> streamSupplier) {
|
||||
return (s, ti, token, a) -> collectFilteredStream(streamSupplier.get(), token);
|
||||
}
|
||||
|
||||
public static <S> SuggestionsSupplier<S> fromCollection(Collection<String> suggestions) {
|
||||
return fromStreamSupplier(suggestions::stream);
|
||||
}
|
||||
|
||||
public static <S> SuggestionsSupplier<S> fromArray(String... suggestions) {
|
||||
return fromStreamSupplier(() -> Arrays.stream(suggestions));
|
||||
}
|
||||
|
||||
|
||||
public static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnum(Class<E> enumClass) {
|
||||
return fromEnumValues(enumClass.getEnumConstants());
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnum(Class<E> enumClass, boolean lowerCase) {
|
||||
return fromEnumValues(lowerCase, enumClass.getEnumConstants());
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnumValues(E... enumValues) {
|
||||
return fromEnumValues(false, enumValues);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnumValues(boolean lowerCase, E... enumValues) {
|
||||
return (s, ti, token, a) -> {
|
||||
Stream<String> st = Arrays.stream(enumValues).map(Enum::name);
|
||||
if (lowerCase)
|
||||
st = st.map(String::toLowerCase);
|
||||
return collectFilteredStream(st, token);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static <S> SuggestionsSupplier<S> booleanValues() {
|
||||
return fromCollection(Arrays.asList("true", "false"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link SuggestionsSupplier} that suggest numbers according to the provided range.
|
||||
*
|
||||
* The current implementation only support range that include either -1 or 1.
|
||||
* @param min
|
||||
* @param max
|
||||
* @return
|
||||
*/
|
||||
public static <S> SuggestionsSupplier<S> fromIntRange(int min, int max) {
|
||||
return fromLongRange(min, max);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link SuggestionsSupplier} that suggest numbers according to the provided range.
|
||||
*
|
||||
* The current implementation only support range that include either -1 or 1.
|
||||
* @param min
|
||||
* @param max
|
||||
* @return
|
||||
*/
|
||||
public static <S> SuggestionsSupplier<S> fromLongRange(long min, long max) {
|
||||
if (max < min) {
|
||||
throw new IllegalArgumentException("min should be less or equals than max");
|
||||
}
|
||||
return (s, ti, token, a) -> {
|
||||
try {
|
||||
List<Long> proposedValues = new ArrayList<>();
|
||||
if (token.length() == 0) {
|
||||
long start = Math.max(Math.max(Math.min(-4, max - 9), min), -9);
|
||||
long end = Math.min(Math.min(start + 9, max), 9);
|
||||
ListUtil.addLongRangeToList(proposedValues, start, end);
|
||||
}
|
||||
else if (token.length() == 1) {
|
||||
if (token.charAt(0) == '0') {
|
||||
if (min > 0 || max < 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
else
|
||||
return Collections.singletonList(token);
|
||||
}
|
||||
else if (token.charAt(0) == '-') {
|
||||
ListUtil.addLongRangeToList(proposedValues, Math.max(-9, min), -1);
|
||||
}
|
||||
else {
|
||||
long lToken = Long.parseLong(token);
|
||||
if (lToken > max) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
lToken *= 10;
|
||||
if (lToken > max) {
|
||||
return Collections.singletonList(token);
|
||||
}
|
||||
|
||||
ListUtil.addLongRangeToList(proposedValues, lToken, Math.min(lToken + 9, max));
|
||||
}
|
||||
}
|
||||
else {
|
||||
long lToken = Long.parseLong(token);
|
||||
if (lToken < min || lToken > max) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
lToken *= 10;
|
||||
if (lToken < min || lToken > max) {
|
||||
return Collections.singletonList(token);
|
||||
}
|
||||
|
||||
if (lToken < 0) {
|
||||
ListUtil.addLongRangeToList(proposedValues, Math.max(lToken - 9, min), lToken);
|
||||
}
|
||||
else {
|
||||
ListUtil.addLongRangeToList(proposedValues, lToken, Math.min(lToken + 9, max));
|
||||
}
|
||||
}
|
||||
|
||||
return collectFilteredStream(proposedValues.stream().map(i -> i.toString()), token);
|
||||
} catch (NumberFormatException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link SuggestionsSupplier} that support greedy strings argument using the suggestion from this {@link SuggestionsSupplier}.
|
||||
* @param index the index of the first argument of the greedy string argument
|
||||
* @return
|
||||
*/
|
||||
public default SuggestionsSupplier<S> greedyString(int index) {
|
||||
|
||||
return (s, ti, token, args) -> {
|
||||
|
||||
if (ti < index)
|
||||
return Collections.emptyList();
|
||||
|
||||
String gToken = AbstractCommand.getLastParams(args, index);
|
||||
String[] splitGToken = gToken.split(" ", -1);
|
||||
int currentTokenPosition = splitGToken.length - 1;
|
||||
String[] prevWordsGToken = Arrays.copyOf(splitGToken, currentTokenPosition);
|
||||
|
||||
String[] argsWithMergedGreedyToken = Arrays.copyOf(args, index + 1);
|
||||
argsWithMergedGreedyToken[index] = gToken;
|
||||
|
||||
List<String> currentTokenProposal = new ArrayList<>();
|
||||
for (String suggestion : getSuggestions(s, index, gToken, argsWithMergedGreedyToken)) {
|
||||
String[] splitSuggestion = suggestion.split(" ", -1);
|
||||
if (splitSuggestion.length <= currentTokenPosition)
|
||||
continue;
|
||||
if (!Arrays.equals(Arrays.copyOf(splitGToken, currentTokenPosition), prevWordsGToken))
|
||||
continue;
|
||||
if (splitSuggestion[currentTokenPosition].isEmpty())
|
||||
continue;
|
||||
|
||||
currentTokenProposal.add(splitSuggestion[currentTokenPosition]);
|
||||
}
|
||||
return currentTokenProposal;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public default SuggestionsSupplier<S> quotableString() {
|
||||
return (s, ti, token, a) -> {
|
||||
boolean startWithQuote = token.length() > 0 && (token.charAt(0) == '"' || token.charAt(0) == '\'');
|
||||
String realToken = startWithQuote ? unescapeBrigadierQuotable(token.substring(1), token.charAt(0)) : token;
|
||||
String[] argsCopy = Arrays.copyOf(a, a.length);
|
||||
argsCopy[a.length - 1] = realToken;
|
||||
List<String> rawResults = getSuggestions(s, ti, realToken, argsCopy);
|
||||
|
||||
boolean needsQuotes = false;
|
||||
for (String res : rawResults) {
|
||||
if (!isAllowedInBrigadierUnquotedString(res)) {
|
||||
needsQuotes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return needsQuotes
|
||||
? rawResults.stream().map(SuggestionsSupplier::escapeBrigadierQuotable).collect(Collectors.toList())
|
||||
: rawResults;
|
||||
};
|
||||
}
|
||||
|
||||
// inspired from com.mojang.brigadier.StringReader#readQuotedString()
|
||||
static String unescapeBrigadierQuotable(String input, char quote) {
|
||||
StringBuilder builder = new StringBuilder(input.length());
|
||||
boolean escaped = false;
|
||||
for (char c : input.toCharArray()) {
|
||||
if (escaped) {
|
||||
if (c == quote || c == '\\') {
|
||||
escaped = false;
|
||||
} else {
|
||||
builder.append('\\');
|
||||
}
|
||||
builder.append(c);
|
||||
} else if (c == '\\') {
|
||||
escaped = true;
|
||||
} else if (c == quote) {
|
||||
return builder.toString();
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
// from com.mojang.brigadier.StringReader#isAllowedInUnquotedString(char)
|
||||
static boolean isAllowedInBrigadierUnquotedString(char c) {
|
||||
return c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'
|
||||
|| c == '_' || c == '-' || c == '.' || c == '+';
|
||||
}
|
||||
static boolean isAllowedInBrigadierUnquotedString(String s) {
|
||||
for (char c : s.toCharArray())
|
||||
if (!isAllowedInBrigadierUnquotedString(c))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static String escapeBrigadierQuotable(final String input) {
|
||||
final StringBuilder result = new StringBuilder("\"");
|
||||
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
final char c = input.charAt(i);
|
||||
if (c == '\\' || c == '"') {
|
||||
result.append('\\');
|
||||
}
|
||||
result.append(c);
|
||||
}
|
||||
|
||||
result.append("\"");
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public default SuggestionsSupplier<S> requires(Predicate<S> check) {
|
||||
return (s, ti, to, a) -> {
|
||||
return check.test(s) ? getSuggestions(s, ti, to, a) : Collections.emptyList();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new {@link SuggestionsSupplier} containing all the element of this instance then the element of the provided one,
|
||||
* with all duplicated values removed using {@link Stream#distinct()}.
|
||||
* @param other
|
||||
* @return
|
||||
*/
|
||||
public default SuggestionsSupplier<S> merge(SuggestionsSupplier<S> other) {
|
||||
return (s, ti, to, a) -> {
|
||||
List<String> l1 = getSuggestions(s, ti, to, a);
|
||||
List<String> l2 = other.getSuggestions(s, ti, to, a);
|
||||
return Stream.concat(l1.stream(), l2.stream())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new {@link SuggestionsSupplier} containing all the suggestions of this instance,
|
||||
* but if this list is still empty, returns the suggestions from the provided one.
|
||||
* @param other
|
||||
* @return
|
||||
*/
|
||||
public default SuggestionsSupplier<S> orIfEmpty(SuggestionsSupplier<S> other) {
|
||||
return (s, ti, to, a) -> {
|
||||
List<String> l1 = getSuggestions(s, ti, to, a);
|
||||
return !l1.isEmpty() ? l1 : other.getSuggestions(s, ti, to, a);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
145
Core/src/main/java/fr/pandacube/util/config/AbstractConfig.java
Normal file
145
Core/src/main/java/fr/pandacube/util/config/AbstractConfig.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package fr.pandacube.util.config;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import fr.pandacube.util.Log;
|
||||
import fr.pandacube.util.text_display.ChatColorUtil;
|
||||
/**
|
||||
* Classe chargeant en mémoire un fichier de configuration ou un dossier donné
|
||||
* @author Marc Baloup
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractConfig {
|
||||
|
||||
/**
|
||||
* Correspond au dossier ou au fichier de configuration traité par la sous-classe
|
||||
* courante de {@link AbstractConfig}
|
||||
*/
|
||||
protected File configFile;
|
||||
|
||||
/**
|
||||
* @param fileOrDirName le nom du fichier ou du dossier correspondant à la sous-classe de {@link AbstractConfig}
|
||||
* @param isDir <code>true</code> si il s'agit d'un dossier, <code>false</code> sinon
|
||||
* @throws IOException si le fichier est impossible à créer
|
||||
*/
|
||||
public AbstractConfig(File configDir, String fileOrDirName, FileType type) throws IOException {
|
||||
configFile = new File(configDir, fileOrDirName);
|
||||
if (type == FileType.DIR)
|
||||
configFile.mkdir();
|
||||
else
|
||||
configFile.createNewFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne toutes les lignes d'un fichier donné
|
||||
* @param ignoreEmpty <code>true</code> si on doit ignorer les lignes vides
|
||||
* @param ignoreHashtagComment <code>true</code> si on doit ignorer les lignes commentés (commençant par un #)
|
||||
* @param trimOutput <code>true</code> si on doit appeller la méthode String.trim() sur chaque ligne retournée
|
||||
* @param f le fichier à lire
|
||||
* @return la liste des lignes utiles
|
||||
* @throws IOException
|
||||
*/
|
||||
protected List<String> getFileLines(boolean ignoreEmpty, boolean ignoreHashtagComment, boolean trimOutput, File f) throws IOException {
|
||||
if (!f.isFile())
|
||||
return null;
|
||||
|
||||
BufferedReader reader = new BufferedReader(new FileReader(f));
|
||||
|
||||
List<String> lines = new ArrayList<>();
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
String trimmedLine = line.trim();
|
||||
|
||||
if (ignoreEmpty && trimmedLine.equals(""))
|
||||
continue;
|
||||
|
||||
if (ignoreHashtagComment && trimmedLine.startsWith("#"))
|
||||
continue;
|
||||
|
||||
if (trimOutput)
|
||||
lines.add(trimmedLine);
|
||||
else
|
||||
lines.add(line);
|
||||
}
|
||||
|
||||
|
||||
reader.close();
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retourne toutes les lignes du fichier de configuration
|
||||
* @param ignoreEmpty <code>true</code> si on doit ignorer les lignes vides
|
||||
* @param ignoreHashtagComment <code>true</code> si on doit ignorer les lignes commentés (commençant par un #)
|
||||
* @param trimOutput <code>true</code> si on doit appeller la méthode String.trim() sur chaque ligne retournée
|
||||
* @return la liste des lignes utiles
|
||||
* @throws IOException
|
||||
*/
|
||||
protected List<String> getFileLines(boolean ignoreEmpty, boolean ignoreHashtagComment, boolean trimOutput) throws IOException {
|
||||
return getFileLines(ignoreEmpty, ignoreHashtagComment, trimOutput, configFile);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected List<File> getFileList() {
|
||||
if (!configFile.isDirectory())
|
||||
return null;
|
||||
|
||||
return Arrays.asList(configFile.listFiles());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Découpe une chaine de caractère contenant une série de noeuds
|
||||
* de permissions séparés par des point-virgules et la retourne sous forme d'une liste.
|
||||
* @param perms la chaine de permissions à traiter
|
||||
* @return <code>null</code> si le paramètre est nulle ou si <code>perms.equals("*")</code>, ou alors la chaine splittée.
|
||||
*/
|
||||
public static List<String> splitPermissionsString(String perms) {
|
||||
if (perms == null || perms.equals("*"))
|
||||
return null;
|
||||
return getSplittedString(perms, ";");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static List<String> getSplittedString(String value, String split) {
|
||||
return Collections.unmodifiableList(Arrays.asList(value.split(split)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String getTranslatedColorCode(String string) {
|
||||
return ChatColorUtil.translateAlternateColorCodes('&', string);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected void warning(String message) {
|
||||
Log.warning("Erreur dans la configuration de '"+configFile.getName()+"' : "+message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected enum FileType {
|
||||
FILE, DIR
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package fr.pandacube.util.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class AbstractConfigManager {
|
||||
|
||||
protected final File configDir;
|
||||
|
||||
public AbstractConfigManager(File configD) throws IOException {
|
||||
configDir = configD;
|
||||
|
||||
configDir.mkdirs();
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation must close all closeable configuration (saving for example)
|
||||
* @throws IOException
|
||||
*/
|
||||
public abstract void close() throws IOException;
|
||||
|
||||
/**
|
||||
* Implementation must init all config data
|
||||
* @throws IOException
|
||||
*/
|
||||
public abstract void init() throws IOException;
|
||||
|
||||
|
||||
|
||||
|
||||
public synchronized void reloadConfig() throws IOException {
|
||||
close();
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
package fr.pandacube.util.measurement;
|
||||
|
||||
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 += ".";
|
||||
for (int i = 0; i < precision; i++)
|
||||
precisionFormat += "0";
|
||||
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"), µM(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;
|
||||
|
||||
private DistanceUnit(double mult, String s) {
|
||||
multiplicator = mult;
|
||||
unitStr = s;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
package fr.pandacube.util.measurement;
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
|
||||
}
|
30
Core/src/main/java/fr/pandacube/util/measurement/Tick.java
Normal file
30
Core/src/main/java/fr/pandacube/util/measurement/Tick.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package fr.pandacube.util.measurement;
|
||||
|
||||
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 seconds the duration in second
|
||||
* @return the same duration as provided, but in Minecraft server ticks
|
||||
*/
|
||||
public static long min(long minutes) {
|
||||
return minutes * 1200;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
353
Core/src/main/java/fr/pandacube/util/measurement/TimeUtil.java
Normal file
353
Core/src/main/java/fr/pandacube/util/measurement/TimeUtil.java
Normal file
@@ -0,0 +1,353 @@
|
||||
package fr.pandacube.util.measurement;
|
||||
|
||||
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.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
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;
|
||||
|
||||
import fr.pandacube.Pandacube;
|
||||
import fr.pandacube.util.StringUtil;
|
||||
import fr.pandacube.util.commands.SuggestionsSupplier;
|
||||
|
||||
public class TimeUtil {
|
||||
|
||||
|
||||
private static DateTimeFormatter cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Pandacube.LOCALE);
|
||||
private static DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Pandacube.LOCALE);
|
||||
private static DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Pandacube.LOCALE);
|
||||
private static DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Pandacube.LOCALE);
|
||||
private static DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Pandacube.LOCALE);
|
||||
private static DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Pandacube.LOCALE);
|
||||
|
||||
private static DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Pandacube.LOCALE);
|
||||
private static DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Pandacube.LOCALE);
|
||||
|
||||
|
||||
|
||||
|
||||
public static String relativeDateFr(long displayTime, boolean showSeconds, 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 (timeDiffSec > -60) {
|
||||
if (showSeconds)
|
||||
return "dans " + (-timeDiffSec) + " secondes";
|
||||
else
|
||||
return "dans moins d’une minute";
|
||||
}
|
||||
if (timeDiffSec > -60*2) // dans 2 min
|
||||
return "dans ̈" + (int)Math.floor((-timeDiffSec)/60) + " minute";
|
||||
if (timeDiffSec > -3600) // dans 1h
|
||||
return "dans " + (int)Math.floor((-timeDiffSec)/60) + " minutes";
|
||||
if (timeDiffSec > -3600*2) // dans 2h
|
||||
return "dans " + (int)Math.floor((-timeDiffSec)/(3600)) + " heure";
|
||||
if (timeDiffSec > -3600*12) // dans 12h
|
||||
return "dans " + (int)Math.floor((-timeDiffSec)/(3600)) + " heures";
|
||||
|
||||
LocalDateTime nextMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0).plusDays(1);
|
||||
if (displayDateTime.isBefore(nextMidnight)) // aujourd'hui
|
||||
return "aujourd’hui à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
|
||||
if (displayDateTime.isBefore(nextMidnight.plusDays(1))) // demain
|
||||
return "demain à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
|
||||
if (displayDateTime.isBefore(nextMidnight.plusDays(5))) // dans moins d'1 semaine
|
||||
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " "
|
||||
+ dayOfMonthFormatter.format(displayDateTime) + " à "
|
||||
+ (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
|
||||
|
||||
return fullDateFr(displayTime, showSeconds, true, compactWords);
|
||||
|
||||
}
|
||||
else {
|
||||
// present and past
|
||||
if (timeDiffSec <= 1)
|
||||
return "maintenant";
|
||||
if (timeDiffSec < 60) { // ya moins d'1 min
|
||||
if (showSeconds)
|
||||
return "il y a " + timeDiffSec + " secondes";
|
||||
else
|
||||
return "il y a moins d’une minute";
|
||||
}
|
||||
if (timeDiffSec < 60*2) // ya moins de 2 min
|
||||
return "il y a " + (int)Math.floor((timeDiffSec)/60) + " minute";
|
||||
if (timeDiffSec < 3600) // ya moins d'1h
|
||||
return "il y a " + (int)Math.floor((timeDiffSec)/60) + " minutes";
|
||||
if (timeDiffSec < 3600*2) // ya moins de 2h
|
||||
return "il y a " + (int)Math.floor((timeDiffSec)/(3600)) + " heure";
|
||||
if (timeDiffSec < 3600*12) // ya moins de 12h
|
||||
return "il y a " + (int)Math.floor((timeDiffSec)/(3600)) + " heures";
|
||||
|
||||
LocalDateTime lastMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0);
|
||||
|
||||
if (!displayDateTime.isBefore(lastMidnight)) // aujourd'hui
|
||||
return "aujourd’hui à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
|
||||
if (!displayDateTime.isBefore(lastMidnight.minusDays(1))) // hier
|
||||
return "hier à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
|
||||
if (!displayDateTime.isBefore(lastMidnight.minusDays(6))) // ya moins d'1 semaine
|
||||
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " dernier à "
|
||||
+ (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
|
||||
|
||||
return fullDateFr(displayTime, showSeconds, true, compactWords);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static String fullDateFr(long displayTime, boolean showSeconds, boolean showWeekday, boolean compactWords) {
|
||||
LocalDateTime displayDateTime = toLocalDateTime(displayTime);
|
||||
return (showWeekday ? ((compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " ") : "")
|
||||
+ dayOfMonthFormatter.format(displayDateTime) + " "
|
||||
+ (compactWords ? cmpMonthFormatter : monthFormatter).format(displayDateTime) + " "
|
||||
+ yearFormatter.format(displayDateTime) + " à "
|
||||
+ (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
|
||||
}
|
||||
|
||||
|
||||
private static LocalDateTime toLocalDateTime(long msTime) {
|
||||
return Instant.ofEpochMilli(msTime).atZone(Pandacube.TIMEZONE.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((u1, u2) -> u2.compareTo(u1))
|
||||
.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) {
|
||||
switch (u) {
|
||||
case DAYS:
|
||||
return fr ? "j" : "d";
|
||||
case HOURS:
|
||||
return "h";
|
||||
case MINUTES:
|
||||
return "m";
|
||||
case SECONDS:
|
||||
return "s";
|
||||
case MILLISECONDS:
|
||||
return "ms";
|
||||
case MICROSECONDS:
|
||||
return "μs";
|
||||
case NANOSECONDS:
|
||||
return "ns";
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid TimeUnit: " + Objects.toString(u));
|
||||
}
|
||||
}
|
||||
|
||||
public static int timeUnitToLeftPadLength(TimeUnit u) {
|
||||
switch (u) {
|
||||
case NANOSECONDS:
|
||||
case MICROSECONDS:
|
||||
case MILLISECONDS:
|
||||
return 3;
|
||||
case SECONDS:
|
||||
case MINUTES:
|
||||
case HOURS:
|
||||
return 2;
|
||||
case DAYS:
|
||||
default:
|
||||
return 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 StringUtil.repeat("0", 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
|
||||
* @return
|
||||
*/
|
||||
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
|
||||
* @return
|
||||
*/
|
||||
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
|
||||
* @return
|
||||
*/
|
||||
public static String durationToParsableString(long msDuration) {
|
||||
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, false, false, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @see {@link com.earth2me.essentials.utils.DateUtil#parseDuration(String, boolean)}
|
||||
*/
|
||||
public static long parseDuration(String time, boolean future) throws Exception {
|
||||
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 (int i = 0; i < tokenChars.length; i++) {
|
||||
char c = tokenChars[i];
|
||||
if (Character.isDigit(c)) {
|
||||
scanAndRemovePastSuffixes(remainingSuffixes, accSuffix);
|
||||
accSuffix = "";
|
||||
continue;
|
||||
}
|
||||
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 List<String> allSuffixes = Arrays.asList("y", "mo", "w", "d", "h", "m", "s");
|
||||
private static 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))) {
|
||||
for (int j = i; j >= 0; j--) {
|
||||
suffixes.remove(j);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
66
Core/src/main/java/fr/pandacube/util/net/Array8Bit.java
Normal file
66
Core/src/main/java/fr/pandacube/util/net/Array8Bit.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package fr.pandacube.util.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
|
||||
* @param i
|
||||
* @return
|
||||
*/
|
||||
public boolean getBit(int i) {
|
||||
return values[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* i = 0 is the lowest significant bit
|
||||
* @param i
|
||||
* @param b
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
273
Core/src/main/java/fr/pandacube/util/net/ByteBuffer.java
Normal file
273
Core/src/main/java/fr/pandacube/util/net/ByteBuffer.java
Normal file
@@ -0,0 +1,273 @@
|
||||
package fr.pandacube.util.net;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import fr.pandacube.Pandacube;
|
||||
|
||||
public final class ByteBuffer implements Cloneable {
|
||||
|
||||
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.
|
||||
* @return
|
||||
*/
|
||||
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
|
||||
* @return
|
||||
*/
|
||||
public ByteBuffer putString(String s) {
|
||||
if (s == null) {
|
||||
return putInt(-1);
|
||||
}
|
||||
return putSizedByteArray(s.getBytes(Pandacube.NETWORK_CHARSET));
|
||||
}
|
||||
|
||||
/**
|
||||
* returned string can be null
|
||||
* @return
|
||||
*/
|
||||
public String getString() {
|
||||
byte[] binaryString = getSizedByteArray();
|
||||
return (binaryString == null) ? null : new String(binaryString, Pandacube.NETWORK_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param list The list can be null, and any String can be null too.
|
||||
* @return
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
57
Core/src/main/java/fr/pandacube/util/net/PPacket.java
Normal file
57
Core/src/main/java/fr/pandacube/util/net/PPacket.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package fr.pandacube.util.net;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class PPacket {
|
||||
public String name;
|
||||
/* package */ int id;
|
||||
public 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]);
|
||||
}
|
||||
}
|
44
Core/src/main/java/fr/pandacube/util/net/PPacketAnswer.java
Normal file
44
Core/src/main/java/fr/pandacube/util/net/PPacketAnswer.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package fr.pandacube.util.net;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class PPacketAnswer extends PPacket {
|
||||
/* package */ 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package fr.pandacube.util.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
|
||||
*/
|
||||
public void onPacketReceive(PSocket connection, P packet);
|
||||
|
||||
}
|
165
Core/src/main/java/fr/pandacube/util/net/PServer.java
Normal file
165
Core/src/main/java/fr/pandacube/util/net/PServer.java
Normal file
@@ -0,0 +1,165 @@
|
||||
package fr.pandacube.util.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 org.apache.commons.lang.builder.ToStringBuilder;
|
||||
|
||||
import fr.pandacube.Pandacube;
|
||||
import fr.pandacube.util.Log;
|
||||
|
||||
public class PServer extends Thread implements Closeable {
|
||||
private static AtomicInteger connectionCounterId = new AtomicInteger(0);
|
||||
|
||||
private int port;
|
||||
private ServerSocket socket;
|
||||
private String socketName;
|
||||
|
||||
private List<TCPServerClientConnection> clients = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
private AtomicBoolean isClosed = new AtomicBoolean(false);
|
||||
|
||||
|
||||
private List<PPacketListener<PPacket>> globalPacketListeners = Collections.synchronizedList(new ArrayList<>());
|
||||
private List<PSocketConnectionListener> clientConnectionListeners = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
|
||||
|
||||
private String password;
|
||||
|
||||
public PServer(int port, String sckName, String password) throws IOException {
|
||||
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(Pandacube.NETWORK_TCP_BUFFER_SIZE);
|
||||
socket.setPerformancePreferences(0, 1, 0);
|
||||
socket.bind(new InetSocketAddress(port));
|
||||
|
||||
while (true) {
|
||||
Socket socketClient = socket.accept();
|
||||
socketClient.setSendBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
|
||||
socketClient.setSoTimeout(Pandacube.NETWORK_TIMEOUT);
|
||||
|
||||
try {
|
||||
@SuppressWarnings("resource")
|
||||
TCPServerClientConnection co = new TCPServerClientConnection(socketClient,
|
||||
connectionCounterId.getAndIncrement());
|
||||
co.start();
|
||||
} catch (IOException e) {
|
||||
Log.severe("Connexion impossible avec " + socketClient.getInetAddress());
|
||||
}
|
||||
}
|
||||
} catch(SocketException e) {
|
||||
|
||||
} 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) throws IOException {
|
||||
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 e) {}
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return isClosed.get() || socket.isClosed();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public List<PSocket> getClients() {
|
||||
synchronized (clients) {
|
||||
return new ArrayList<>(clients);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("thread", getName())
|
||||
.append("socket", socket).toString();
|
||||
}
|
||||
|
||||
}
|
351
Core/src/main/java/fr/pandacube/util/net/PSocket.java
Normal file
351
Core/src/main/java/fr/pandacube/util/net/PSocket.java
Normal file
@@ -0,0 +1,351 @@
|
||||
package fr.pandacube.util.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 org.apache.commons.lang.builder.ToStringBuilder;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import fr.pandacube.Pandacube;
|
||||
import fr.pandacube.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 {
|
||||
|
||||
private boolean server = false;
|
||||
private Socket socket;
|
||||
private SocketAddress addr;
|
||||
private DataInputStream in;
|
||||
private DataOutputStream out;
|
||||
private Object outSynchronizer = new Object();
|
||||
private String password;
|
||||
|
||||
private AtomicBoolean isClosed = new AtomicBoolean(false);
|
||||
|
||||
private List<PPacketListener<PPacket>> packetListeners = Collections.synchronizedList(new ArrayList<>());
|
||||
private List<PSocketConnectionListener> connectionListeners = Collections.synchronizedList(new ArrayList<>());
|
||||
private 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 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(Pandacube.NETWORK_TCP_BUFFER_SIZE);
|
||||
socket.setSendBufferSize(Pandacube.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
|
||||
password = null;
|
||||
}
|
||||
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(Pandacube.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
|
||||
* @throws IOException
|
||||
*
|
||||
*/
|
||||
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, Pandacube.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.
|
||||
* @param packet
|
||||
* @throws IOException
|
||||
*/
|
||||
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(Pandacube.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 e) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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 new ToStringBuilder(this)
|
||||
.append("thread", getName())
|
||||
.append("socket", socket.getRemoteSocketAddress()).toString();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package fr.pandacube.util.net;
|
||||
|
||||
public interface PSocketConnectionListener {
|
||||
|
||||
/**
|
||||
* Called when a socket is connected
|
||||
* @param connection the connection
|
||||
*/
|
||||
public void onConnect(PSocket connection);
|
||||
|
||||
/**
|
||||
* Called just before a socket is disconnected
|
||||
* @param connection the connection
|
||||
*/
|
||||
public void onDisconnect(PSocket connection);
|
||||
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package fr.pandacube.util.network_api.client;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
@Deprecated
|
||||
public abstract class AbstractRequest {
|
||||
|
||||
private final String pass;
|
||||
private 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();
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package fr.pandacube.util.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;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package fr.pandacube.util.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 = 0;
|
||||
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 = 0;
|
||||
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.");
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
package fr.pandacube.util.network_api.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import fr.pandacube.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);
|
||||
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
package fr.pandacube.util.network_api.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
@Deprecated
|
||||
public class NetworkAPIListener implements Runnable {
|
||||
|
||||
private int port = 0;
|
||||
String pass;
|
||||
private ServerSocket serverSocket;
|
||||
private HashMap<String, AbstractRequestExecutor> requestExecutors = new HashMap<>();
|
||||
private 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
|
||||
* @param peh PacketExecutionHandler permettant de prendre en charge
|
||||
* l'exécution asynchrone d'une requête reçu pas un client
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("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 e) {}
|
||||
|
||||
synchronized (this) {
|
||||
try {
|
||||
if (!serverSocket.isClosed()) serverSocket.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
|
||||
System.out.println("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 e) {}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
package fr.pandacube.util.network_api.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import fr.pandacube.util.Log;
|
||||
import fr.pandacube.util.network_api.server.RequestAnalyser.BadRequestException;
|
||||
|
||||
/**
|
||||
* 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 Socket socket;
|
||||
private 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 e1) {}
|
||||
if (e instanceof IOException)
|
||||
Log.warning("Unable to read packet from socket " + socket + ": " + e.toString());
|
||||
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 e) {}
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
package fr.pandacube.util.network_api.server;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.Socket;
|
||||
|
||||
@Deprecated
|
||||
public class RequestAnalyser {
|
||||
|
||||
private NetworkAPIListener networkAPIListener;
|
||||
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");
|
||||
|
||||
networkAPIListener = napiListener;
|
||||
|
||||
// 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(networkAPIListener.pass)) throw new BadRequestException("wrong_password");
|
||||
|
||||
// lecture de la deuxième ligne
|
||||
line = in.readLine();
|
||||
if (line == null || networkAPIListener.getRequestExecutor(line) == null)
|
||||
throw new BadRequestException("command_not_exists");
|
||||
command = line;
|
||||
|
||||
// lecture de la troisième ligne
|
||||
line = in.readLine();
|
||||
|
||||
int data_size = 0;
|
||||
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 = 0;
|
||||
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 class BadRequestException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public BadRequestException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package fr.pandacube.util.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();
|
||||
}
|
||||
}
|
84
Core/src/main/java/fr/pandacube/util/orm/DBConnection.java
Normal file
84
Core/src/main/java/fr/pandacube/util/orm/DBConnection.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import fr.pandacube.util.Log;
|
||||
|
||||
public class DBConnection {
|
||||
private static final long CONNECTION_CHECK_TIMEOUT = 30000; // in ms
|
||||
|
||||
private Connection conn;
|
||||
private String url;
|
||||
private String login;
|
||||
private String pass;
|
||||
|
||||
private long timeOfLastCheck = 0;
|
||||
|
||||
public DBConnection(String host, int port, String dbname, String l, String p)
|
||||
throws ClassNotFoundException, SQLException {
|
||||
Class.forName("com.mysql.jdbc.Driver");
|
||||
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 ? false : 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 e) {}
|
||||
}
|
||||
|
||||
}
|
391
Core/src/main/java/fr/pandacube/util/orm/ORM.java
Normal file
391
Core/src/main/java/fr/pandacube/util/orm/ORM.java
Normal file
@@ -0,0 +1,391 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
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.function.Consumer;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
import fr.pandacube.util.Log;
|
||||
|
||||
/**
|
||||
* <b>ORM = Object-Relational Mapping</b>
|
||||
*
|
||||
* @author Marc Baloup
|
||||
*
|
||||
*/
|
||||
public final class ORM {
|
||||
|
||||
private static List<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
|
||||
private static Map<Class<? extends SQLElement<?>>, String> tableNames = new HashMap<>();
|
||||
|
||||
private static DBConnection connection;
|
||||
|
||||
public static DBConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public synchronized static <E extends SQLElement<E>> void init(DBConnection conn) {
|
||||
|
||||
connection = conn;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static synchronized <E extends SQLElement<E>> void initTable(Class<E> elemClass) throws ORMInitTableException {
|
||||
if (tables.contains(elemClass)) return;
|
||||
try {
|
||||
tables.add(elemClass);
|
||||
Log.debug("[ORM] Start Init SQL table "+elemClass.getSimpleName());
|
||||
E instance = elemClass.getConstructor().newInstance();
|
||||
String tableName = instance.tableName();
|
||||
tableNames.put(elemClass, tableName);
|
||||
if (!tableExistInDB(tableName)) createTable(instance);
|
||||
Log.debug("[ORM] End init SQL table "+elemClass.getSimpleName());
|
||||
} catch (Exception|ExceptionInInitializerError e) {
|
||||
throw new ORMInitTableException(elemClass, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static <E extends SQLElement<E>> void createTable(E elem) throws SQLException {
|
||||
|
||||
String sql = "CREATE TABLE IF NOT EXISTS " + elem.tableName() + " (";
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
Collection<SQLField<E, ?>> tableFields = elem.getFields().values();
|
||||
boolean first = true;
|
||||
for (SQLField<E, ?> f : tableFields) {
|
||||
Pair<String, List<Object>> statementPart = f.forSQLPreparedStatement();
|
||||
params.addAll(statementPart.getValue1());
|
||||
|
||||
if (!first) sql += ", ";
|
||||
first = false;
|
||||
sql += statementPart.getValue0();
|
||||
}
|
||||
|
||||
sql += ", PRIMARY KEY id(id))";
|
||||
|
||||
try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql)) {
|
||||
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 ORMException {
|
||||
initTable(elemClass);
|
||||
return tableNames.get(elemClass);
|
||||
}
|
||||
|
||||
private static boolean tableExistInDB(String tableName) throws SQLException {
|
||||
boolean exist = false;
|
||||
try (ResultSet set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null)) {
|
||||
exist = set.next();
|
||||
}
|
||||
return exist;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E extends SQLElement<E>> SQLField<E, Integer> getSQLIdField(Class<E> elemClass)
|
||||
throws ORMInitTableException {
|
||||
initTable(elemClass);
|
||||
return (SQLField<E, Integer>) SQLElement.fieldsCache.get(elemClass).get("id");
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Integer... ids) throws ORMException {
|
||||
return getByIds(elemClass, Arrays.asList(ids));
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Collection<Integer> ids)
|
||||
throws ORMException {
|
||||
return getAll(elemClass, getSQLIdField(elemClass).in(ids), SQLOrderBy.asc(getSQLIdField(elemClass)), 1, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getById(Class<E> elemClass, int id) throws ORMException {
|
||||
return getFirst(elemClass, getSQLIdField(elemClass).eq(id));
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where)
|
||||
throws ORMException {
|
||||
return getFirst(elemClass, where, null, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLOrderBy<E> orderBy)
|
||||
throws ORMException {
|
||||
return getFirst(elemClass, null, orderBy, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy)
|
||||
throws ORMException {
|
||||
return getFirst(elemClass, where, orderBy, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy, Integer offset)
|
||||
throws ORMException {
|
||||
SQLElementList<E> elts = getAll(elemClass, where, orderBy, 1, offset);
|
||||
return (elts.size() == 0) ? null : elts.get(0);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass) throws ORMException {
|
||||
return getAll(elemClass, null, null, null, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where) throws ORMException {
|
||||
return getAll(elemClass, where, null, null, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy) throws ORMException {
|
||||
return getAll(elemClass, where, orderBy, null, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy, Integer limit) throws ORMException {
|
||||
return getAll(elemClass, where, orderBy, limit, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy, Integer limit, Integer offset) throws ORMException {
|
||||
SQLElementList<E> elmts = new SQLElementList<>();
|
||||
forEach(elemClass, where, orderBy, limit, offset, elmts::add);
|
||||
return elmts;
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, Consumer<E> action) throws ORMException {
|
||||
forEach(elemClass, null, null, null, null, action);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
|
||||
Consumer<E> action) throws ORMException {
|
||||
forEach(elemClass, where, null, null, null, action);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy, Consumer<E> action) throws ORMException {
|
||||
forEach(elemClass, where, orderBy, null, null, action);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy, Integer limit, Consumer<E> action) throws ORMException {
|
||||
forEach(elemClass, where, orderBy, limit, null, action);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy, Integer limit, Integer offset, Consumer<E> action) throws ORMException {
|
||||
initTable(elemClass);
|
||||
|
||||
try {
|
||||
String sql = "SELECT * FROM " + getTableName(elemClass);
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (where != null) {
|
||||
Pair<String, List<Object>> ret = where.toSQL();
|
||||
sql += " WHERE " + ret.getValue0();
|
||||
params.addAll(ret.getValue1());
|
||||
}
|
||||
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 ORMException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static <E extends SQLElement<E>> long count(Class<E> elemClass) throws ORMException {
|
||||
return count(elemClass, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> long count(Class<E> elemClass, SQLWhere<E> where) throws ORMException {
|
||||
initTable(elemClass);
|
||||
|
||||
try {
|
||||
String sql = "SELECT COUNT(*) as count FROM " + getTableName(elemClass);
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (where != null) {
|
||||
Pair<String, List<Object>> ret = where.toSQL();
|
||||
sql += " WHERE " + ret.getValue0();
|
||||
params.addAll(ret.getValue1());
|
||||
}
|
||||
sql += ";";
|
||||
|
||||
try (ResultSet set = customQueryStatement(sql, params)) {
|
||||
if (set.next()) {
|
||||
return set.getLong(1);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new ORMException(e);
|
||||
}
|
||||
|
||||
throw new ORMException("Can’t retrieve element count from database (The ResultSet may be empty)");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static ResultSet customQueryStatement(String sql, List<Object> params) throws ORMException {
|
||||
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 ORMException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static <E extends SQLElement<E>> SQLUpdate<E> update(Class<E> elemClass, SQLWhere<E> where) throws ORMException {
|
||||
return new SQLUpdate<>(elemClass, where);
|
||||
}
|
||||
|
||||
/* package */ static <E extends SQLElement<E>> int update(Class<E> elemClass, SQLWhere<E> where, Map<SQLField<E, ?>, Object> values) throws ORMException {
|
||||
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}.
|
||||
* @throws ORMException
|
||||
*/
|
||||
public static <E extends SQLElement<E>> int delete(Class<E> elemClass, SQLWhere<E> where) throws ORMException {
|
||||
initTable(elemClass);
|
||||
|
||||
if (where == null) {
|
||||
return truncateTable(elemClass);
|
||||
}
|
||||
|
||||
Pair<String, List<Object>> whereData = where.toSQL();
|
||||
|
||||
String sql = "DELETE FROM " + getTableName(elemClass)
|
||||
+ " WHERE " + whereData.getValue0()
|
||||
+ ";";
|
||||
List<Object> params = new ArrayList<>(whereData.getValue1());
|
||||
|
||||
return customUpdateStatement(sql, params);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static int customUpdateStatement(String sql, List<Object> params) throws ORMException {
|
||||
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 ORMException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static <E extends SQLElement<E>> int truncateTable(Class<E> elemClass) throws ORMException {
|
||||
try (Statement stmt = connection.getNativeConnection().createStatement()) {
|
||||
return stmt.executeUpdate("TRUNCATE `" + getTableName(elemClass) + "`");
|
||||
} catch(SQLException e) {
|
||||
throw new ORMException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws ORMException {
|
||||
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 ORMException("Error while converting value of field '"+sqlField.getName()+"' with SQLCustomType from "+((SQLCustomType<Object, Object>)sqlField.type).intermediateJavaType
|
||||
+"(jdbc source) to "+sqlField.type.getJavaType()+"(java destination). The original value is '"+val.toString()+"'", e);
|
||||
}
|
||||
}
|
||||
|
||||
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 ORMException(
|
||||
"This SQLElement representing a database entry is not valid for save : " + instance.toString());
|
||||
|
||||
return instance;
|
||||
} catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | SQLException e) {
|
||||
throw new ORMException("Can't instanciate " + elemClass.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private ORM() {} // rend la classe non instanciable
|
||||
|
||||
}
|
18
Core/src/main/java/fr/pandacube/util/orm/ORMException.java
Normal file
18
Core/src/main/java/fr/pandacube/util/orm/ORMException.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
public class ORMException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ORMException(Throwable initCause) {
|
||||
super(initCause);
|
||||
}
|
||||
|
||||
public ORMException(String message, Throwable initCause) {
|
||||
super(message, initCause);
|
||||
}
|
||||
|
||||
public ORMException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
public class ORMInitTableException extends ORMException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/* package */ <E extends SQLElement<E>> ORMInitTableException(Class<E> tableElem) {
|
||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"));
|
||||
}
|
||||
|
||||
/* package */ <E extends SQLElement<E>> ORMInitTableException(Class<E> tableElem, Throwable t) {
|
||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"), t);
|
||||
}
|
||||
|
||||
}
|
25
Core/src/main/java/fr/pandacube/util/orm/SQLCustomType.java
Normal file
25
Core/src/main/java/fr/pandacube/util/orm/SQLCustomType.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
501
Core/src/main/java/fr/pandacube/util/orm/SQLElement.java
Normal file
501
Core/src/main/java/fr/pandacube/util/orm/SQLElement.java
Normal file
@@ -0,0 +1,501 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
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;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import fr.pandacube.util.EnumUtil;
|
||||
import fr.pandacube.util.Log;
|
||||
|
||||
public abstract class SQLElement<E extends SQLElement<E>> {
|
||||
/** cache for fields for each subclass of SQLElement */
|
||||
/* package */ static final Map<Class<? extends SQLElement<?>>, SQLFieldMap<? extends SQLElement<?>>> fieldsCache = new HashMap<>();
|
||||
|
||||
DBConnection db = ORM.getConnection();
|
||||
|
||||
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 {
|
||||
ORM.initTable((Class<E>)getClass());
|
||||
} catch (ORMInitTableException 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.
|
||||
*/
|
||||
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 == null || !(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.toString(), 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.");
|
||||
|
||||
boolean modify = false;
|
||||
if (value == null) {
|
||||
if (sqlField.canBeNull || (sqlField.autoIncrement && !stored)) modify = true;
|
||||
else
|
||||
throw new IllegalArgumentException(
|
||||
"SQLField '" + sqlField.getName() + "' of " + getClass().getName() + " is a NOT NULL field");
|
||||
}
|
||||
else if (sqlField.type.isAssignableFrom(value)) modify = true;
|
||||
else
|
||||
throw new IllegalArgumentException("SQLField '" + sqlField.getName() + "' of " + getClass().getName()
|
||||
+ " type is '" + sqlField.type.toString() + "' and can't accept values of type "
|
||||
+ value.getClass().getName());
|
||||
|
||||
if (modify) 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 ORMException {
|
||||
T fkValue = get(field);
|
||||
if (fkValue == null) return null;
|
||||
return ORM.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 ORMException {
|
||||
T value = get(field.getPrimaryField());
|
||||
if (value == null) return new SQLElementList<>();
|
||||
return ORM.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 ORMException {
|
||||
if (!isValidForSave())
|
||||
throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved.");
|
||||
|
||||
ORM.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;
|
||||
|
||||
ORM.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);
|
||||
|
||||
String concat_vals = "";
|
||||
String concat_fields = "";
|
||||
List<Object> psValues = new ArrayList<>();
|
||||
|
||||
boolean first = true;
|
||||
for (Map.Entry<SQLField<E, ?>, Object> entry : values.entrySet()) {
|
||||
if (!first) {
|
||||
concat_vals += ",";
|
||||
concat_fields += ",";
|
||||
}
|
||||
first = false;
|
||||
concat_vals += " ? ";
|
||||
concat_fields += "`" + entry.getKey().getName() + "`";
|
||||
addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
try (PreparedStatement ps = db.getNativeConnection().prepareStatement(
|
||||
"INSERT INTO " + tableName() + " (" + concat_fields + ") VALUES (" + concat_vals + ")",
|
||||
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 ORMException("Error while saving data", e);
|
||||
}
|
||||
return (E) this;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
protected static <E extends SQLElement<E>> void addValueToSQLObjectList(List<Object> list, SQLField<E, ?> field, Object jValue) throws ORMException {
|
||||
if (jValue != null && field.type instanceof SQLCustomType) {
|
||||
try {
|
||||
jValue = ((SQLCustomType)field.type).javaToDbConv.apply(jValue);
|
||||
} catch (Exception e) {
|
||||
throw new ORMException("Error while converting value of field '"+field.getName()+"' with SQLCustomType from "+field.type.getJavaType()
|
||||
+"(java source) to "+((SQLCustomType<?, ?>)field.type).intermediateJavaType+"(jdbc destination). The original value is '"+jValue.toString()+"'", 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 ORMException {
|
||||
|
||||
if (stored) { // supprimer la ligne de la base
|
||||
try (PreparedStatement st = db.getNativeConnection()
|
||||
.prepareStatement("DELETE FROM " + tableName() + " WHERE id=" + id)) {
|
||||
Log.debug(st.toString());
|
||||
st.executeUpdate();
|
||||
markAsNotStored();
|
||||
} catch (SQLException e) {
|
||||
throw new ORMException(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 static final long serialVersionUID = 1L;
|
||||
|
||||
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() {
|
||||
ToStringBuilder b = new ToStringBuilder(this);
|
||||
|
||||
for (SQLField<E, ?> f : fields.values())
|
||||
try {
|
||||
b.append(f.getName(), get(f));
|
||||
} catch (IllegalArgumentException e) {
|
||||
b.append(f.getName(), "(Undefined)");
|
||||
}
|
||||
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || !(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 super.hashCode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public JsonObject asJsonObject() {
|
||||
JsonObject json = new JsonObject();
|
||||
for (SQLField<E, ?> f : getFields().values()) {
|
||||
json.add(f.getName(), new 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); // can’t 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); // can’t 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);
|
||||
|
||||
@Deprecated
|
||||
public static final SQLType<String> CHAR(int charCount) {
|
||||
if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive.");
|
||||
return new SQLType<>("CHAR(" + charCount + ")", String.class);
|
||||
}
|
||||
|
||||
public static final 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 final <T extends Enum<T>> SQLType<T> ENUM(Class<T> enumType) {
|
||||
if (enumType == null) throw new IllegalArgumentException("enumType can't be null.");
|
||||
String enumStr = "'";
|
||||
boolean first = true;
|
||||
for (T el : enumType.getEnumConstants()) {
|
||||
if (!first) enumStr += "', '";
|
||||
first = false;
|
||||
enumStr += el.name();
|
||||
|
||||
}
|
||||
enumStr += "'";
|
||||
|
||||
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);
|
||||
|
||||
|
||||
}
|
201
Core/src/main/java/fr/pandacube/util/orm/SQLElementList.java
Normal file
201
Core/src/main/java/fr/pandacube/util/orm/SQLElementList.java
Normal file
@@ -0,0 +1,201 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.sql.SQLException;
|
||||
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.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
|
||||
import fr.pandacube.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param <E>
|
||||
*/
|
||||
public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
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 <T>
|
||||
* @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 (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.
|
||||
*
|
||||
* @throws SQLException
|
||||
*/
|
||||
public synchronized int saveCommon() throws ORMException {
|
||||
List<E> storedEl = getStoredEl();
|
||||
if (storedEl.isEmpty()) return 0;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<E> classEl = (Class<E>)storedEl.get(0).getClass();
|
||||
|
||||
int ret = ORM.update(classEl,
|
||||
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(() -> new ArrayList<>()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated please use {@link ORM#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();
|
||||
|
||||
ORM.delete(classEl,
|
||||
storedEl.get(0).getFieldId().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList()))
|
||||
);
|
||||
for (E el : storedEl)
|
||||
el.markAsNotStored();
|
||||
} catch (ORMException e) {
|
||||
Log.severe(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public <T, P extends SQLElement<P>> SQLElementList<P> getReferencedEntries(SQLFKField<E, T, P> foreignKey, SQLOrderBy<P> orderBy) throws ORMException {
|
||||
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 ORM.getAll(foreignKey.getForeignElementClass(), foreignKey.getPrimaryField().in(values), orderBy, null, null);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public <T, P extends SQLElement<P>> Map<T, P> getReferencedEntriesInGroups(SQLFKField<E, T, P> foreignKey) throws ORMException {
|
||||
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 ORMException {
|
||||
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 ORM.getAll(foreignKey.getSQLElementType(), foreignKey.in(values), orderBy, limit, offset);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public <T, F extends SQLElement<F>> Map<T, SQLElementList<F>> getReferencingForeignEntriesInGroups(SQLFKField<F, T, E> foreignKey, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws ORMException {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
69
Core/src/main/java/fr/pandacube/util/orm/SQLFKField.java
Normal file
69
Core/src/main/java/fr/pandacube/util/orm/SQLFKField.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import fr.pandacube.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 = ORM.getSQLIdField(fkEl);
|
||||
return new SQLFKField<>(f.type, nul, deflt, fkEl, f);
|
||||
} catch (ORMInitTableException 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 {
|
||||
ORM.initTable(fkEl);
|
||||
} catch (ORMInitTableException 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;
|
||||
}
|
||||
|
||||
}
|
141
Core/src/main/java/fr/pandacube/util/orm/SQLField.java
Normal file
141
Core/src/main/java/fr/pandacube/util/orm/SQLField.java
Normal file
@@ -0,0 +1,141 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
import fr.pandacube.util.orm.SQLWhereComp.SQLComparator;
|
||||
|
||||
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 */ Pair<String, List<Object>> forSQLPreparedStatement() {
|
||||
List<Object> params = new ArrayList<>(1);
|
||||
if (defaultValue != null && !autoIncrement) params.add(defaultValue);
|
||||
return new Pair<>("`" + 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 {@link #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().getValue0().replaceFirst("\\?",
|
||||
(defaultValue != null && !autoIncrement) ? defaultValue.toString() : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (!(obj instanceof SQLField)) return false;
|
||||
SQLField<?, ?> f = (SQLField<?, ?>) obj;
|
||||
if (!f.getName().equals(getName())) return false;
|
||||
if (!f.sqlElemClass.equals(sqlElemClass)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
97
Core/src/main/java/fr/pandacube/util/orm/SQLOrderBy.java
Normal file
97
Core/src/main/java/fr/pandacube/util/orm/SQLOrderBy.java
Normal file
@@ -0,0 +1,97 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SQLOrderBy<E extends SQLElement<E>> {
|
||||
|
||||
private 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() {
|
||||
String ret = "";
|
||||
boolean first = true;
|
||||
for (OBField f : orderByFields) {
|
||||
if (!first) ret += ", ";
|
||||
first = false;
|
||||
ret += "`" + f.field.getName() + "` " + f.direction.name();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
39
Core/src/main/java/fr/pandacube/util/orm/SQLType.java
Normal file
39
Core/src/main/java/fr/pandacube/util/orm/SQLType.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
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 isAssignableFrom(Object val) {
|
||||
if (javaTypes.isInstance(val)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || !(obj instanceof SQLType)) return false;
|
||||
return toString().equals(((SQLType<?>) obj).toString());
|
||||
}
|
||||
|
||||
public Class<T> getJavaType() {
|
||||
return javaTypes;
|
||||
}
|
||||
|
||||
|
||||
}
|
70
Core/src/main/java/fr/pandacube/util/orm/SQLUpdate.java
Normal file
70
Core/src/main/java/fr/pandacube/util/orm/SQLUpdate.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
import fr.pandacube.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 ORMException {
|
||||
|
||||
if (values.isEmpty()) {
|
||||
Log.warning(new ORMException("Trying to do an UPDATE with no values to SET. Query aborted."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
String sql = "UPDATE " + ORM.getTableName(elemClass) + " SET ";
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
boolean first = true;
|
||||
for (Map.Entry<SQLField<E, ?>, Object> entry : values.entrySet()) {
|
||||
if (!first)
|
||||
sql += ", ";
|
||||
sql += "`" + entry.getKey().getName() + "` = ? ";
|
||||
SQLElement.addValueToSQLObjectList(params, entry.getKey(), entry.getValue());
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (where != null) {
|
||||
Pair<String, List<Object>> ret = where.toSQL();
|
||||
sql += " WHERE " + ret.getValue0();
|
||||
params.addAll(ret.getValue1());
|
||||
}
|
||||
|
||||
sql += ";";
|
||||
|
||||
return ORM.customUpdateStatement(sql, params);
|
||||
}
|
||||
|
||||
}
|
43
Core/src/main/java/fr/pandacube/util/orm/SQLWhere.java
Normal file
43
Core/src/main/java/fr/pandacube/util/orm/SQLWhere.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
import fr.pandacube.util.Log;
|
||||
|
||||
public abstract class SQLWhere<E extends SQLElement<E>> {
|
||||
|
||||
public abstract Pair<String, List<Object>> toSQL() throws ORMException;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return toSQL().getValue0();
|
||||
} catch (ORMException e) {
|
||||
Log.warning(e);
|
||||
return "[SQLWhere.toString() error (see logs)]";
|
||||
}
|
||||
}
|
||||
|
||||
public SQLWhereAnd<E> and(SQLWhere<E> other) {
|
||||
return new SQLWhereAnd<E>().and(this).and(other);
|
||||
}
|
||||
|
||||
public SQLWhereOr<E> or(SQLWhere<E> other) {
|
||||
return new SQLWhereOr<E>().or(this).or(other);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLWhereAnd<E> and() {
|
||||
return new SQLWhereAnd<>();
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLWhereOr<E> or() {
|
||||
return new SQLWhereOr<>();
|
||||
}
|
||||
|
||||
public static String escapeLike(String str) {
|
||||
return str.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%");
|
||||
}
|
||||
|
||||
}
|
15
Core/src/main/java/fr/pandacube/util/orm/SQLWhereAnd.java
Normal file
15
Core/src/main/java/fr/pandacube/util/orm/SQLWhereAnd.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
public class SQLWhereAnd<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
||||
|
||||
/* package */ SQLWhereAnd() {
|
||||
super(SQLBoolOp.AND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLWhereAnd<E> and(SQLWhere<E> other) {
|
||||
add(other);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
58
Core/src/main/java/fr/pandacube/util/orm/SQLWhereChain.java
Normal file
58
Core/src/main/java/fr/pandacube/util/orm/SQLWhereChain.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public abstract class SQLWhereChain<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLBoolOp operator;
|
||||
protected List<SQLWhere<E>> conditions = new ArrayList<>();
|
||||
|
||||
/* package */ SQLWhereChain(SQLBoolOp op) {
|
||||
if (op == null) throw new IllegalArgumentException("op can't be null");
|
||||
operator = op;
|
||||
}
|
||||
|
||||
protected void add(SQLWhere<E> sqlWhere) {
|
||||
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
|
||||
conditions.add(sqlWhere);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() throws ORMException {
|
||||
if (conditions.isEmpty()) {
|
||||
throw new ORMException("SQLWhereChain needs at least one element inside !");
|
||||
}
|
||||
|
||||
String sql = "";
|
||||
List<Object> params = new ArrayList<>();
|
||||
boolean first = true;
|
||||
|
||||
for (SQLWhere<E> w : conditions) {
|
||||
if (!first) sql += " " + operator.sql + " ";
|
||||
first = false;
|
||||
|
||||
Pair<String, List<Object>> ret = w.toSQL();
|
||||
sql += "(" + ret.getValue0() + ")";
|
||||
params.addAll(ret.getValue1());
|
||||
}
|
||||
|
||||
return new Pair<>(sql, params);
|
||||
}
|
||||
|
||||
/* package */ enum SQLBoolOp {
|
||||
/** Equivalent to SQL "<code>AND</code>" */
|
||||
AND("AND"),
|
||||
/** Equivalent to SQL "<code>OR</code>" */
|
||||
OR("OR");
|
||||
/* package */ final String sql;
|
||||
|
||||
private SQLBoolOp(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
58
Core/src/main/java/fr/pandacube/util/orm/SQLWhereComp.java
Normal file
58
Core/src/main/java/fr/pandacube/util/orm/SQLWhereComp.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
/* package */ class SQLWhereComp<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> left;
|
||||
private SQLComparator comp;
|
||||
private Object right;
|
||||
|
||||
/**
|
||||
* Compare a field with a value
|
||||
*
|
||||
* @param l the field at left of the comparison operator. Can't be null
|
||||
* @param c the comparison operator, can't be null
|
||||
* @param r the value at right of the comparison operator. Can't be null
|
||||
*/
|
||||
/* package */ <T> SQLWhereComp(SQLField<E, T> l, SQLComparator c, T r) {
|
||||
if (l == null || r == null || c == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null");
|
||||
left = l;
|
||||
comp = c;
|
||||
right = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() throws ORMException {
|
||||
List<Object> params = new ArrayList<>();
|
||||
SQLElement.addValueToSQLObjectList(params, left, right);
|
||||
return new Pair<>("`" + left.getName() + "` " + comp.sql + " ? ", params);
|
||||
}
|
||||
|
||||
/* package */ enum SQLComparator {
|
||||
/** Equivalent to SQL "<code>=</code>" */
|
||||
EQ("="),
|
||||
/** Equivalent to SQL "<code>></code>" */
|
||||
GT(">"),
|
||||
/** Equivalent to SQL "<code>>=</code>" */
|
||||
GEQ(">="),
|
||||
/** Equivalent to SQL "<code><</code>" */
|
||||
LT("<"),
|
||||
/** Equivalent to SQL "<code><=</code>" */
|
||||
LEQ("<="),
|
||||
/** Equivalent to SQL "<code>!=</code>" */
|
||||
NEQ("!=");
|
||||
|
||||
/* package */ final String sql;
|
||||
|
||||
private SQLComparator(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
38
Core/src/main/java/fr/pandacube/util/orm/SQLWhereIn.java
Normal file
38
Core/src/main/java/fr/pandacube/util/orm/SQLWhereIn.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
/* package */ class SQLWhereIn<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> field;
|
||||
private Collection<?> values;
|
||||
|
||||
/* package */ <T> SQLWhereIn(SQLField<E, T> f, Collection<T> v) {
|
||||
if (f == null || v == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereIn constructor can't be null");
|
||||
field = f;
|
||||
values = v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() throws ORMException {
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (values.isEmpty())
|
||||
return new Pair<>(" 1=0 ", params);
|
||||
|
||||
for (Object v : values)
|
||||
SQLElement.addValueToSQLObjectList(params, field, v);
|
||||
|
||||
char[] questions = new char[values.size() == 0 ? 0 : (values.size() * 2 - 1)];
|
||||
for (int i = 0; i < questions.length; i++)
|
||||
questions[i] = i % 2 == 0 ? '?' : ',';
|
||||
|
||||
return new Pair<>("`" + field.getName() + "` IN (" + new String(questions) + ") ", params);
|
||||
}
|
||||
|
||||
}
|
33
Core/src/main/java/fr/pandacube/util/orm/SQLWhereLike.java
Normal file
33
Core/src/main/java/fr/pandacube/util/orm/SQLWhereLike.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
/* package */ class SQLWhereLike<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> field;
|
||||
private String likeExpr;
|
||||
|
||||
/**
|
||||
* Compare a field with a value
|
||||
*
|
||||
* @param f the field at left of the LIKE keyword. Can't be null
|
||||
* @param like the like expression.
|
||||
*/
|
||||
/* package */ SQLWhereLike(SQLField<E, ?> f, String like) {
|
||||
if (f == null || like == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null");
|
||||
field = f;
|
||||
likeExpr = like;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() {
|
||||
ArrayList<Object> params = new ArrayList<>();
|
||||
params.add(likeExpr);
|
||||
return new Pair<>("`" + field.getName() + "` LIKE ? ", params);
|
||||
}
|
||||
|
||||
}
|
37
Core/src/main/java/fr/pandacube/util/orm/SQLWhereNull.java
Normal file
37
Core/src/main/java/fr/pandacube/util/orm/SQLWhereNull.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
import fr.pandacube.util.Log;
|
||||
|
||||
/* package */ class SQLWhereNull<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private SQLField<E, ?> fild;
|
||||
private boolean nulll;
|
||||
|
||||
/**
|
||||
* Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition.
|
||||
*
|
||||
* @param field the field to check null / not null state
|
||||
* @param isNull true if we want to ckeck if "IS NULL", or false to check if
|
||||
* "IS NOT NULL"
|
||||
*/
|
||||
/* package */ SQLWhereNull(SQLField<E, ?> field, boolean isNull) {
|
||||
if (field == null) throw new IllegalArgumentException("field can't be null");
|
||||
if (!field.canBeNull) Log.getLogger().log(Level.WARNING,
|
||||
"Useless : Trying to check IS [NOT] NULL on the field " + field.getSQLElementType().getName() + "#"
|
||||
+ field.getName() + " which is declared in the ORM as 'can't be null'");
|
||||
fild = field;
|
||||
nulll = isNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() {
|
||||
return new Pair<>("`" + fild.getName() + "` IS " + ((nulll) ? "NULL" : "NOT NULL"), new ArrayList<>());
|
||||
}
|
||||
|
||||
}
|
15
Core/src/main/java/fr/pandacube/util/orm/SQLWhereOr.java
Normal file
15
Core/src/main/java/fr/pandacube/util/orm/SQLWhereOr.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package fr.pandacube.util.orm;
|
||||
|
||||
public class SQLWhereOr<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
||||
|
||||
/* package */ SQLWhereOr() {
|
||||
super(SQLBoolOp.OR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLWhereOr<E> or(SQLWhere<E> other) {
|
||||
add(other);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
187
Core/src/main/java/fr/pandacube/util/search/SearchEngine.java
Normal file
187
Core/src/main/java/fr/pandacube/util/search/SearchEngine.java
Normal file
@@ -0,0 +1,187 @@
|
||||
package fr.pandacube.util.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
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.util.Log;
|
||||
|
||||
/**
|
||||
* Utility class to manage searching among a set of
|
||||
* SearchResult instances, using case insensitive
|
||||
* keywords.
|
||||
*/
|
||||
public class SearchEngine<R extends SearchResult> {
|
||||
|
||||
Map<String, Set<R>> searchKeywordsResultMap = new HashMap<>();
|
||||
Map<R, Set<String>> resultsSearchKeywordsMap = new HashMap<>();
|
||||
|
||||
Map<String, Set<R>> suggestionsKeywordsResultMap = new HashMap<>();
|
||||
Map<R, Set<String>> resultsSuggestionsKeywordsMap = new HashMap<>();
|
||||
|
||||
Set<R> resultSet = new HashSet<>();
|
||||
|
||||
private Cache<Set<String>, List<String>> suggestionsCache;
|
||||
|
||||
public SearchEngine(int suggestionsCacheSize) {
|
||||
suggestionsCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(suggestionsCacheSize)
|
||||
.build();
|
||||
}
|
||||
|
||||
public synchronized void addResult(R result) {
|
||||
if (result == null)
|
||||
throw new IllegalArgumentException("Provided result cannot be null.");
|
||||
if (resultSet.contains(result))
|
||||
return;
|
||||
|
||||
Set<String> searchKw;
|
||||
try {
|
||||
searchKw = result.getSearchKeywords();
|
||||
Preconditions.checkNotNull(searchKw, "SearchResult instance must provide a non null set of search keywords");
|
||||
searchKw = searchKw.stream()
|
||||
.filter(e -> e != null)
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toSet());
|
||||
} catch (Exception e) {
|
||||
Log.severe(e);
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> suggestsKw;
|
||||
try {
|
||||
suggestsKw = result.getSuggestionKeywords();
|
||||
Preconditions.checkNotNull(suggestsKw, "SearchResult instance must provide a non null set of suggestions keywords");
|
||||
suggestsKw = new HashSet<>(suggestsKw);
|
||||
suggestsKw.removeIf(e -> e == null);
|
||||
} catch (Exception e) {
|
||||
Log.severe(e);
|
||||
return;
|
||||
}
|
||||
|
||||
resultSet.add(result);
|
||||
|
||||
for (String skw : searchKw) {
|
||||
searchKeywordsResultMap.computeIfAbsent(skw, s -> new HashSet<>()).add(result);
|
||||
}
|
||||
|
||||
resultsSearchKeywordsMap.put(result, searchKw);
|
||||
|
||||
resultsSuggestionsKeywordsMap.put(result, suggestsKw);
|
||||
|
||||
for (String skw : suggestsKw) {
|
||||
suggestionsKeywordsResultMap.computeIfAbsent(skw, s -> new HashSet<>()).add(result);
|
||||
}
|
||||
|
||||
suggestionsCache.invalidateAll();
|
||||
}
|
||||
|
||||
public synchronized void removeResult(R result) {
|
||||
if (result == null || !resultSet.contains(result))
|
||||
return;
|
||||
|
||||
resultSet.remove(result);
|
||||
|
||||
Set<String> searchKw = resultsSearchKeywordsMap.remove(result);
|
||||
if (searchKw != null) {
|
||||
for (String skw : searchKw) {
|
||||
Set<R> set = searchKeywordsResultMap.get(skw);
|
||||
if (set == null)
|
||||
continue;
|
||||
set.remove(result);
|
||||
if (set.isEmpty())
|
||||
searchKeywordsResultMap.remove(skw);
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> suggestsKw = resultsSearchKeywordsMap.remove(result);
|
||||
if (suggestsKw != null) {
|
||||
for (String skw : suggestsKw) {
|
||||
Set<R> set = suggestionsKeywordsResultMap.get(skw);
|
||||
if (set == null)
|
||||
continue;
|
||||
set.remove(result);
|
||||
if (set.isEmpty())
|
||||
suggestionsKeywordsResultMap.remove(skw);
|
||||
}
|
||||
}
|
||||
|
||||
resultsSuggestionsKeywordsMap.remove(result);
|
||||
|
||||
suggestionsCache.invalidateAll();
|
||||
}
|
||||
|
||||
public synchronized Set<R> search(Set<String> searchTerms) {
|
||||
if (searchTerms == null)
|
||||
searchTerms = new HashSet<String>();
|
||||
|
||||
Set<R> retainedResults = new HashSet<>(resultSet);
|
||||
for (String term : searchTerms) {
|
||||
retainedResults.retainAll(search(term));
|
||||
}
|
||||
|
||||
return retainedResults;
|
||||
}
|
||||
|
||||
public synchronized Set<R> search(String searchTerm) {
|
||||
if (searchTerm == null || searchTerm.isEmpty()) {
|
||||
return new HashSet<>(resultSet);
|
||||
}
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
Set<R> retainedResults = new HashSet<>();
|
||||
for (String skw : searchKeywordsResultMap.keySet()) {
|
||||
if (skw.contains(searchTerm)) {
|
||||
retainedResults.addAll(new ArrayList<>(searchKeywordsResultMap.get(skw)));
|
||||
}
|
||||
}
|
||||
|
||||
return retainedResults;
|
||||
}
|
||||
|
||||
public synchronized List<String> suggestKeywords(List<String> prevSearchTerms) {
|
||||
if (prevSearchTerms == null || prevSearchTerms.isEmpty()) {
|
||||
return new ArrayList<>(suggestionsKeywordsResultMap.keySet());
|
||||
}
|
||||
Set<String> lowerCaseSearchTerm = prevSearchTerms.stream()
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
try {
|
||||
return suggestionsCache.get(lowerCaseSearchTerm, (Callable<List<String>>) () -> {
|
||||
Set<R> prevResults = search(lowerCaseSearchTerm);
|
||||
|
||||
Set<String> suggestions = new HashSet<>();
|
||||
for (R prevRes : prevResults) {
|
||||
suggestions.addAll(new ArrayList<>(resultsSuggestionsKeywordsMap.get(prevRes)));
|
||||
}
|
||||
|
||||
suggestions.removeIf(s -> {
|
||||
for (String st : lowerCaseSearchTerm)
|
||||
if (s.contains(st))
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
return new ArrayList<>(suggestions);
|
||||
});
|
||||
} catch (ExecutionException e) {
|
||||
Log.severe(e);
|
||||
return new ArrayList<>(suggestionsKeywordsResultMap.keySet());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// TODO sort results
|
||||
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package fr.pandacube.util.search;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface SearchResult {
|
||||
|
||||
public Set<String> getSearchKeywords();
|
||||
|
||||
public Set<String> getSuggestionKeywords();
|
||||
|
||||
}
|
309
Core/src/main/java/fr/pandacube/util/text_display/Chat.java
Normal file
309
Core/src/main/java/fr/pandacube/util/text_display/Chat.java
Normal file
@@ -0,0 +1,309 @@
|
||||
package fr.pandacube.util.text_display;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.UUID;
|
||||
|
||||
import fr.pandacube.Pandacube;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.ItemTag;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.chat.hover.content.Content;
|
||||
import net.md_5.bungee.api.chat.hover.content.Entity;
|
||||
import net.md_5.bungee.api.chat.hover.content.Item;
|
||||
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||
|
||||
public abstract class Chat extends ChatStatic {
|
||||
|
||||
protected BaseComponent component;
|
||||
protected boolean console = false;
|
||||
|
||||
public Chat(BaseComponent c) {
|
||||
component = c;
|
||||
}
|
||||
|
||||
public BaseComponent get() {
|
||||
return component;
|
||||
}
|
||||
|
||||
public BaseComponent[] getAsArray() {
|
||||
return new BaseComponent[] { component };
|
||||
}
|
||||
|
||||
public String getLegacyText() {
|
||||
return component.toLegacyText();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public Chat then(BaseComponent subComponent) {
|
||||
// here are some optimizations to avoid unnecessary component nesting
|
||||
if (subComponent instanceof TextComponent) {
|
||||
TextComponent txtComp = (TextComponent) subComponent;
|
||||
if (!txtComp.hasFormatting() && (txtComp.getText() == null || txtComp.getText().isEmpty())) {
|
||||
// no need to add the provided component to the current component.
|
||||
// but eventual child component must be added
|
||||
if (txtComp.getExtra() != null) {
|
||||
for (BaseComponent child : txtComp.getExtra())
|
||||
then(child);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
component.addExtra(subComponent);
|
||||
return this;
|
||||
}
|
||||
public Chat then(Chat comp) { return then(comp.get()); }
|
||||
public Chat then(BaseComponent[] components) {
|
||||
if (components != null) {
|
||||
for (BaseComponent c : components) {
|
||||
then(c);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Chat thenText(Object plainText) { return then(text(plainText)); }
|
||||
|
||||
public Chat thenInfo(Object plainText) { return then(infoText(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 thenNewLine() { return thenText("\n"); }
|
||||
|
||||
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, String value) { return then(score(name, objective, value)); }
|
||||
|
||||
|
||||
|
||||
|
||||
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(Pandacube.CHAT_DECORATION_CHAR, Pandacube.CHAT_DECORATION_COLOR, 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(" ").get(), Pandacube.CHAT_DECORATION_CHAR,
|
||||
Pandacube.CHAT_DECORATION_COLOR, Pandacube.CHAT_NB_CHAR_MARGIN, 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(" ").get(), Pandacube.CHAT_DECORATION_CHAR,
|
||||
Pandacube.CHAT_DECORATION_COLOR, Pandacube.CHAT_NB_CHAR_MARGIN, 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(" ").get(), Pandacube.CHAT_DECORATION_CHAR,
|
||||
Pandacube.CHAT_DECORATION_COLOR, 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 class FormatableChat extends Chat {
|
||||
public FormatableChat(BaseComponent c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
public FormatableChat console(boolean c) { console = c; return this; }
|
||||
|
||||
public FormatableChat color(ChatColor c) { component.setColor(c); return this; }
|
||||
public FormatableChat color(Color c) { return color(ChatColor.of(c)); }
|
||||
public FormatableChat color(String c) { return color(ChatColor.of(c)); }
|
||||
|
||||
public FormatableChat black() { return color(ChatColor.BLACK); }
|
||||
public FormatableChat darkBlue() { return color(ChatColor.DARK_BLUE); }
|
||||
public FormatableChat darkGreen() { return color(ChatColor.DARK_GREEN); }
|
||||
public FormatableChat darkAqua() { return color(ChatColor.DARK_AQUA); }
|
||||
public FormatableChat darkRed() { return color(ChatColor.DARK_RED); }
|
||||
public FormatableChat darkPurple() { return color(ChatColor.DARK_PURPLE); }
|
||||
public FormatableChat gold() { return color(ChatColor.GOLD); }
|
||||
public FormatableChat gray() { return color(ChatColor.GRAY); }
|
||||
public FormatableChat darkGray() { return color(ChatColor.DARK_GRAY); }
|
||||
public FormatableChat blue() { return color(ChatColor.BLUE); }
|
||||
public FormatableChat green() { return color(ChatColor.GREEN); }
|
||||
public FormatableChat aqua() { return color(ChatColor.AQUA); }
|
||||
public FormatableChat red() { return color(ChatColor.RED); }
|
||||
public FormatableChat lightPurple() { return color(ChatColor.LIGHT_PURPLE); }
|
||||
public FormatableChat yellow() { return color(ChatColor.YELLOW); }
|
||||
public FormatableChat white() { return color(ChatColor.WHITE); }
|
||||
|
||||
public FormatableChat successColor() { return color(Pandacube.CHAT_SUCCESS_COLOR); }
|
||||
public FormatableChat failureColor() { return color(Pandacube.CHAT_FAILURE_COLOR); }
|
||||
public FormatableChat infoColor() { return color(Pandacube.CHAT_INFO_COLOR); }
|
||||
public FormatableChat dataColor() { return color(Pandacube.CHAT_DATA_COLOR); }
|
||||
public FormatableChat decorationColor() { return color(Pandacube.CHAT_DECORATION_COLOR); }
|
||||
|
||||
public FormatableChat font(String f) { component.setFont(f); return this; }
|
||||
|
||||
public FormatableChat bold(Boolean b) { component.setBold(b); return this; }
|
||||
public FormatableChat bold() { return bold(true); }
|
||||
|
||||
public FormatableChat italic(Boolean i) { component.setItalic(i); return this; }
|
||||
public FormatableChat italic() { return italic(true); }
|
||||
|
||||
public FormatableChat underlined(Boolean u) { component.setUnderlined(u); return this; }
|
||||
public FormatableChat underlined() { return underlined(true); }
|
||||
|
||||
public FormatableChat strikethrough(Boolean s) { component.setStrikethrough(s); return this; }
|
||||
public FormatableChat strikethrough() { return strikethrough(true); }
|
||||
|
||||
public FormatableChat obfuscated(Boolean o) { component.setObfuscated(o); return this; }
|
||||
public FormatableChat obfuscated() { return obfuscated(true); }
|
||||
|
||||
public FormatableChat shiftClickInsertion(String i) { component.setInsertion(i); return this; }
|
||||
|
||||
private FormatableChat clickEvent(ClickEvent e) { component.setClickEvent(e); return this; }
|
||||
private FormatableChat clickEvent(ClickEvent.Action a, String v) { return clickEvent(new ClickEvent(a, v)); }
|
||||
public FormatableChat clickCommand(String cmdWithSlash) { return clickEvent(ClickEvent.Action.RUN_COMMAND, cmdWithSlash); }
|
||||
public FormatableChat clickSuggest(String cmdWithSlash) { return clickEvent(ClickEvent.Action.SUGGEST_COMMAND, cmdWithSlash); }
|
||||
public FormatableChat clickClipboard(String value) { return clickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, value); }
|
||||
public FormatableChat clickURL(String url) { return clickEvent(ClickEvent.Action.OPEN_URL, url); }
|
||||
public FormatableChat clickBookPage(int page) { return clickEvent(ClickEvent.Action.CHANGE_PAGE, Integer.toString(page)); }
|
||||
|
||||
private FormatableChat hoverEvent(HoverEvent e) { component.setHoverEvent(e); return this; }
|
||||
private FormatableChat hoverEvent(HoverEvent.Action a, Content v) { return hoverEvent(new HoverEvent(a, v)); }
|
||||
private FormatableChat hoverText(Text v) { return hoverEvent(HoverEvent.Action.SHOW_TEXT, v); }
|
||||
@SuppressWarnings("deprecation")
|
||||
public FormatableChat hoverText(BaseComponent v) {
|
||||
try {
|
||||
return hoverText(new Text( new BaseComponent[] {v}));
|
||||
} catch (NoSuchMethodError e) {
|
||||
return hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new BaseComponent[] {v}));
|
||||
}
|
||||
}
|
||||
public FormatableChat hoverText(Chat v) { return hoverText(v.get()); }
|
||||
public FormatableChat hoverText(String legacyText) { return hoverText(legacyText(legacyText)); }
|
||||
private FormatableChat hoverItem(Item v) { return hoverEvent(HoverEvent.Action.SHOW_ITEM, v); }
|
||||
/** @param id namespaced item id */
|
||||
public FormatableChat hoverItem(String id, int stackSize, ItemTag tag) { return hoverItem(new Item(id, stackSize, tag)); }
|
||||
/** @param id namespaced item id */
|
||||
public FormatableChat hoverItem(String id, int stackSize) { return hoverItem(id, stackSize, null); }
|
||||
/** @param id namespaced item id */
|
||||
public FormatableChat hoverItem(String id, ItemTag tag) { return hoverItem(id, -1, tag); }
|
||||
/** @param id namespaced item id */
|
||||
public FormatableChat hoverItem(String id) { return hoverItem(id, -1, null); }
|
||||
public FormatableChat hoverEntity(Entity e) { return hoverEvent(HoverEvent.Action.SHOW_ENTITY, e); }
|
||||
/** @param type namespaced entity type
|
||||
* @param id cannot be null */
|
||||
public FormatableChat hoverEntity(String type, UUID id, BaseComponent displayName) { return hoverEntity(new Entity(type, id.toString(), displayName)); }
|
||||
/** @param type namespaced entity type
|
||||
* @param id cannot be null */
|
||||
public FormatableChat hoverEntity(String type, UUID id) { return hoverEntity(type, id, null); }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static Object[] filterChatToBaseComponent(Object[] values) {
|
||||
if (values == null)
|
||||
return null;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
Object v = values[i];
|
||||
if (v instanceof Chat)
|
||||
values[i] = ((Chat) v).get();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,293 @@
|
||||
package fr.pandacube.util.text_display;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
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 Pattern HEX_COLOR_PATTERN = Pattern.compile("§x(?>§[0-9a-f]){6}", Pattern.CASE_INSENSITIVE);
|
||||
private static Pattern ESS_COLOR_PATTERN = Pattern.compile("§#[0-9a-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.
|
||||
* @param legacyText
|
||||
* @return
|
||||
*/
|
||||
public static String getLastColors(String legacyText) {
|
||||
String result = "";
|
||||
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 = rgb + result;
|
||||
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 = rgb + result;
|
||||
break;
|
||||
}
|
||||
|
||||
// try detect non-rgb format
|
||||
char colorChar = legacyText.charAt(index + 1);
|
||||
ChatColor legacyColor = getChatColorByChar(colorChar);
|
||||
|
||||
if (legacyColor != null) {
|
||||
result = legacyColor.toString() + result;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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-FR0-9])", "§$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 ChatColor interpolateColor(float v0, float v1, float v, ChatColor cc0, ChatColor cc1) {
|
||||
Color c0 = cc0.getColor(), c1 = cc1.getColor();
|
||||
int r0 = c0.getRed(), g0 = c0.getGreen(), b0 = c0.getBlue(),
|
||||
r1 = c1.getRed(), g1 = c1.getGreen(), b1 = c1.getBlue();
|
||||
float normV = (v - v0) / (v1 - v0);
|
||||
return ChatColor.of(new Color(
|
||||
(int) (r0 + (r1 - r0) * normV),
|
||||
(int) (g0 + (g1 - g0) * normV),
|
||||
(int) (b0 + (b1 - b0) * normV)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class ChatValueGradient {
|
||||
List<Pair<Float, ChatColor>> colors = new ArrayList<>();
|
||||
|
||||
public synchronized ChatValueGradient add(float v, ChatColor col) {
|
||||
colors.add(Pair.with(v, col));
|
||||
return this;
|
||||
}
|
||||
|
||||
public synchronized ChatColor 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).getValue1();
|
||||
|
||||
colors.sort((p1, p2) -> Float.compare(p1.getValue0(), p2.getValue0()));
|
||||
|
||||
if (v <= colors.get(0).getValue0())
|
||||
return colors.get(0).getValue1();
|
||||
if (v >= colors.get(colors.size() - 1).getValue0())
|
||||
return colors.get(colors.size() - 1).getValue1();
|
||||
|
||||
int p1 = 1;
|
||||
for (; p1 < colors.size(); p1++) {
|
||||
if (colors.get(p1).getValue0() >= v)
|
||||
break;
|
||||
}
|
||||
int p0 = p1 - 1;
|
||||
float v0 = colors.get(p0).getValue0(), v1 = colors.get(p1).getValue0();
|
||||
ChatColor cc0 = colors.get(p0).getValue1(), cc1 = colors.get(p1).getValue1();
|
||||
|
||||
return interpolateColor(v0, v1, v, cc0, cc1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
package fr.pandacube.util.text_display;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import fr.pandacube.util.text_display.Chat.FormatableChat;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.KeybindComponent;
|
||||
import net.md_5.bungee.api.chat.Keybinds;
|
||||
import net.md_5.bungee.api.chat.ScoreComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
|
||||
public abstract class ChatStatic {
|
||||
|
||||
|
||||
|
||||
public static FormatableChat chatComponent(BaseComponent c) {
|
||||
return new FormatableChat(c);
|
||||
}
|
||||
|
||||
public static FormatableChat chat() {
|
||||
return chatComponent(new TextComponent());
|
||||
}
|
||||
|
||||
public static FormatableChat chatComponent(BaseComponent[] c) {
|
||||
return chatComponent(new TextComponent(c));
|
||||
}
|
||||
|
||||
public static FormatableChat text(Object plainText) {
|
||||
return chatComponent(new TextComponent(Objects.toString(plainText)));
|
||||
}
|
||||
|
||||
public static FormatableChat legacyText(Object legacyText) {
|
||||
return chatComponent(TextComponent.fromLegacyText(Objects.toString(legacyText), null));
|
||||
}
|
||||
|
||||
public static FormatableChat infoText(Object plainText) {
|
||||
return text(plainText).infoColor();
|
||||
}
|
||||
|
||||
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) {
|
||||
return legacyText(legacyText).white();
|
||||
}
|
||||
|
||||
public static FormatableChat translation(String key, Object... with) {
|
||||
return chatComponent(new TranslatableComponent(key, Chat.filterChatToBaseComponent(with)));
|
||||
}
|
||||
|
||||
/** @param key one of the values in {@link Keybinds}. */
|
||||
public static FormatableChat keybind(String key) {
|
||||
return chatComponent(new KeybindComponent(key));
|
||||
}
|
||||
|
||||
public static FormatableChat score(String name, String objective, String value) {
|
||||
return chatComponent(new ScoreComponent(name, objective, value));
|
||||
}
|
||||
}
|
539
Core/src/main/java/fr/pandacube/util/text_display/ChatUtil.java
Normal file
539
Core/src/main/java/fr/pandacube/util/text_display/ChatUtil.java
Normal file
@@ -0,0 +1,539 @@
|
||||
package fr.pandacube.util.text_display;
|
||||
|
||||
import static fr.pandacube.util.text_display.Chat.chatComponent;
|
||||
import static fr.pandacube.util.text_display.ChatStatic.chat;
|
||||
import static fr.pandacube.util.text_display.ChatStatic.legacyText;
|
||||
import static fr.pandacube.util.text_display.ChatStatic.text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import fr.pandacube.Pandacube;
|
||||
import fr.pandacube.util.text_display.Chat.FormatableChat;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
|
||||
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 CONSOLE_NB_CHAR_DEFAULT = 50;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static BaseComponent createURLLink(String text, String url) {
|
||||
return createURLLink(legacyText(text), url, null);
|
||||
}
|
||||
|
||||
public static BaseComponent createURLLink(String text, String url, String hoverText) {
|
||||
return createURLLink(legacyText(text), url, hoverText != null ? legacyText(hoverText) : null);
|
||||
}
|
||||
|
||||
/* package */ static BaseComponent createURLLink(Chat element, String url, Chat hover) {
|
||||
String dispURL = (url.length() > 50) ? (url.substring(0, 48) + "...") : url;
|
||||
return chat()
|
||||
.clickURL(url)
|
||||
.color(Pandacube.CHAT_URL_COLOR)
|
||||
.hoverText(
|
||||
hover != null ? hover : Chat.text(dispURL)
|
||||
)
|
||||
.then(element)
|
||||
.get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static BaseComponent createCommandLink(String text, String commandWithSlash, String hoverText) {
|
||||
return createCommandLink(text, commandWithSlash, hoverText == null ? null : legacyText(hoverText));
|
||||
}
|
||||
public static BaseComponent createCommandLink(String text, String commandWithSlash, Chat hoverText) {
|
||||
return createCommandLink(legacyText(text), commandWithSlash, hoverText);
|
||||
}
|
||||
|
||||
/* package */ static BaseComponent createCommandLink(Chat d, String commandWithSlash, Chat hoverText) {
|
||||
FormatableChat c = chat()
|
||||
.clickCommand(commandWithSlash)
|
||||
.color(Pandacube.CHAT_COMMAND_COLOR);
|
||||
if (hoverText != null)
|
||||
c.hoverText(hoverText);
|
||||
return c.then(d).get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static BaseComponent createCommandSuggest(String text, String commandWithSlash, String hoverText) {
|
||||
return createCommandSuggest(text, commandWithSlash, hoverText == null ? null : legacyText(hoverText));
|
||||
}
|
||||
public static BaseComponent createCommandSuggest(String text, String commandWithSlash, Chat hoverText) {
|
||||
return createCommandSuggest(legacyText(text), commandWithSlash, hoverText);
|
||||
}
|
||||
|
||||
/* package */ static BaseComponent createCommandSuggest(Chat d, String commandWithSlash, Chat hoverText) {
|
||||
FormatableChat c = chat()
|
||||
.clickSuggest(commandWithSlash)
|
||||
.color(Pandacube.CHAT_COMMAND_COLOR);
|
||||
if (hoverText != null)
|
||||
c.hoverText(hoverText);
|
||||
return c.then(d).get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param cmdFormat the command with %d inside to be replaced with the page number (must start with slash)
|
||||
*/
|
||||
public static BaseComponent 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 = chatComponent(createCommandLink(Integer.toString(page), String.format(cmdFormat, page), "Aller à la page " + page));
|
||||
if (page == currentPage) {
|
||||
pDisp.color(Pandacube.CHAT_COMMAND_HIGHLIGHTED_COLOR);
|
||||
}
|
||||
d.then(pDisp);
|
||||
|
||||
previous = page;
|
||||
}
|
||||
|
||||
|
||||
return d.get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static BaseComponent centerText(BaseComponent text, char repeatedChar, ChatColor decorationColor,
|
||||
boolean console) {
|
||||
|
||||
int textWidth = componentWidth(text, console);
|
||||
int maxWidth = (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH;
|
||||
|
||||
if (textWidth > maxWidth)
|
||||
return text;
|
||||
|
||||
int repeatedCharWidth = charW(repeatedChar, console, false);
|
||||
int sideWidth = (maxWidth - textWidth) / 2;
|
||||
int sideNbChar = sideWidth / repeatedCharWidth;
|
||||
|
||||
if (sideNbChar == 0)
|
||||
return text;
|
||||
|
||||
String sideChars = repeatedChar(repeatedChar, sideNbChar);
|
||||
|
||||
Chat d = Chat.chat()
|
||||
.then(text(sideChars).color(decorationColor))
|
||||
.then(text);
|
||||
if (repeatedChar != ' ')
|
||||
d.then(text(sideChars).color(decorationColor));
|
||||
|
||||
return d.get();
|
||||
|
||||
}
|
||||
|
||||
public static BaseComponent leftText(BaseComponent text, char repeatedChar, ChatColor decorationColor, int nbLeft,
|
||||
boolean console) {
|
||||
|
||||
int textWidth = componentWidth(text, console);
|
||||
int maxWidth = (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH;
|
||||
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.get();
|
||||
|
||||
}
|
||||
|
||||
public static BaseComponent rightText(BaseComponent text, char repeatedChar, ChatColor decorationColor, int nbRight,
|
||||
boolean console) {
|
||||
|
||||
int textWidth = componentWidth(text, console);
|
||||
int maxWidth = (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH;
|
||||
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.get();
|
||||
|
||||
}
|
||||
|
||||
public static BaseComponent emptyLine(char repeatedChar, ChatColor decorationColor, boolean console) {
|
||||
int count = ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH) / charW(repeatedChar, console, false);
|
||||
return text(repeatedChar(repeatedChar, count)).color(decorationColor).get();
|
||||
}
|
||||
|
||||
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(BaseComponent[] components, boolean console) {
|
||||
if (components == null)
|
||||
return 0;
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (BaseComponent c : components)
|
||||
count += componentWidth(c, console);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static int componentWidth(BaseComponent component, boolean console) {
|
||||
if (component == null)
|
||||
return 0;
|
||||
|
||||
int count = 0;
|
||||
|
||||
if (component instanceof TextComponent) {
|
||||
count += strWidth(((TextComponent)component).getText(), console, component.isBold());
|
||||
}
|
||||
else if (component instanceof TranslatableComponent) {
|
||||
for (BaseComponent c : ((TranslatableComponent)component).getWith())
|
||||
count += componentWidth(c, console);
|
||||
}
|
||||
|
||||
if (component.getExtra() != null) {
|
||||
for (BaseComponent c : component.getExtra())
|
||||
count += componentWidth(c, console);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static int strWidth(String str, boolean console, boolean bold) {
|
||||
int count = 0;
|
||||
for (char c : str.toCharArray())
|
||||
count += charW(c, console, bold);
|
||||
return (count < 0) ? 0 : count;
|
||||
}
|
||||
|
||||
private 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<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;
|
||||
|
||||
String currentWord = "";
|
||||
int currentWordSize = 0;
|
||||
boolean bold = false;
|
||||
boolean firstCharCurrentWorldBold = false;
|
||||
|
||||
do {
|
||||
char c = legacyText.charAt(index);
|
||||
if (c == ChatColor.COLOR_CHAR && index < legacyText.length() - 1) {
|
||||
currentWord += c;
|
||||
c = legacyText.charAt(++index);
|
||||
currentWord += 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 = currentWord.substring(1);
|
||||
currentWordSize -= charW(' ', false, firstCharCurrentWorldBold);
|
||||
}
|
||||
currentLine = (lastStyle.equals("§r") ? "" : lastStyle) + currentWord;
|
||||
currentLineSize = currentWordSize;
|
||||
}
|
||||
else {
|
||||
currentLine += currentWord;
|
||||
currentLineSize += currentWordSize;
|
||||
}
|
||||
currentWord = ""+c;
|
||||
currentWordSize = charW(c, false, bold);
|
||||
firstCharCurrentWorldBold = 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 = currentWord.substring(1);
|
||||
currentWordSize -= charW(' ', false, firstCharCurrentWorldBold);
|
||||
}
|
||||
currentLine = (lastStyle.equals("§r") ? "" : lastStyle) + currentWord;
|
||||
currentLineSize = currentWordSize;
|
||||
}
|
||||
else {
|
||||
currentLine += currentWord;
|
||||
currentLineSize += currentWordSize;
|
||||
}
|
||||
// wrap after
|
||||
lines.add(currentLine);
|
||||
String lastStyle = ChatColorUtil.getLastColors(currentLine);
|
||||
|
||||
currentLine = lastStyle.equals("§r") ? "" : lastStyle;
|
||||
currentLineSize = 0;
|
||||
currentWord = "";
|
||||
currentWordSize = 0;
|
||||
firstCharCurrentWorldBold = bold;
|
||||
}
|
||||
else {
|
||||
currentWord += c;
|
||||
currentWordSize += charW(c, false, bold);
|
||||
}
|
||||
|
||||
} while(++index < legacyText.length());
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static BaseComponent toUniqueBaseComponent(BaseComponent... baseComponents) {
|
||||
if (baseComponents == null || baseComponents.length == 0)
|
||||
return new TextComponent();
|
||||
if (baseComponents.length == 1)
|
||||
return baseComponents[0];
|
||||
return new TextComponent(baseComponents);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generate a tree view based on the tree structure {@code node}.
|
||||
*
|
||||
* Each element in the returned array represent 1 line of the tree view.
|
||||
* Thus, the caller may send each line separately or at once depending of the quantity of data.
|
||||
* @param node
|
||||
* @return A array of component, each element being a single line.
|
||||
*/
|
||||
public static BaseComponent[] treeView(DisplayTreeNode node, boolean console) {
|
||||
List<TextComponent> ret = treeView_(node, console);
|
||||
return ret.toArray(new BaseComponent[ret.size()]);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
private static List<TextComponent> treeView_(DisplayTreeNode node, boolean console) {
|
||||
List<TextComponent> ret = new ArrayList<>();
|
||||
|
||||
TextComponent curr = new TextComponent();
|
||||
curr.addExtra(node.component);
|
||||
curr.setText("");
|
||||
|
||||
ret.add(curr);
|
||||
|
||||
for (int i = 0; i < node.children.size(); i++) {
|
||||
List<TextComponent> childComponents = treeView_(node.children.get(i), console);
|
||||
boolean last = i == node.children.size() - 1;
|
||||
for (int j = 0; j < childComponents.size(); j++) {
|
||||
TextComponent cComp = childComponents.get(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));
|
||||
cComp.setText(prefix + cComp.getText());
|
||||
ret.add(cComp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class DisplayTreeNode {
|
||||
public final BaseComponent component;
|
||||
public final List<DisplayTreeNode> children = new ArrayList<>();
|
||||
|
||||
public DisplayTreeNode(BaseComponent cmp) {
|
||||
component = cmp;
|
||||
}
|
||||
|
||||
public DisplayTreeNode addChild(DisplayTreeNode child) {
|
||||
children.add(child);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
package fr.pandacube.util.text_display;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
public class TextProgressBar {
|
||||
private static String pattern_start = "[";
|
||||
private static String pattern_end = "]";
|
||||
private static ChatColor color_empty = ChatColor.DARK_GRAY;
|
||||
private static ChatColor color_default = ChatColor.RESET;
|
||||
private static String pattern_empty = ".";
|
||||
private static String pattern_full = "|";
|
||||
|
||||
public static String progressBar(double[] values, ChatColor[] colors, double total, int nbCar) {
|
||||
long[] sizes = new long[values.length];
|
||||
|
||||
int max_size = nbCar - pattern_start.length() - pattern_end.length();
|
||||
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
double sum_values_before = 0;
|
||||
for (int j = i; j >= 0; j--)
|
||||
sum_values_before += values[j];
|
||||
|
||||
long car_position = Math.round(max_size * sum_values_before / total);
|
||||
|
||||
// évite les barre de progressions plus grandes que la taille
|
||||
// demandée
|
||||
if (car_position > max_size) car_position = max_size;
|
||||
|
||||
long sum_sizes_before = 0;
|
||||
for (int j = i - 1; j >= 0; j--)
|
||||
sum_sizes_before += sizes[j];
|
||||
|
||||
sizes[i] = car_position - sum_sizes_before;
|
||||
}
|
||||
int sum_sizes = 0;
|
||||
|
||||
String bar = pattern_start;
|
||||
for (int i = 0; i < sizes.length; i++) {
|
||||
sum_sizes += sizes[i];
|
||||
|
||||
ChatColor color = color_default;
|
||||
if (colors != null && i < colors.length && colors[i] != null) color = colors[i];
|
||||
|
||||
bar = bar + color;
|
||||
|
||||
for (int j = 0; j < sizes[i]; j++)
|
||||
bar = bar + pattern_full;
|
||||
}
|
||||
|
||||
bar = bar + color_empty;
|
||||
for (int j = 0; j < (max_size - sum_sizes); j++)
|
||||
bar = bar + pattern_empty;
|
||||
|
||||
bar = bar + ChatColor.RESET + pattern_end;
|
||||
return bar;
|
||||
}
|
||||
|
||||
public static String progressBar(double value, ChatColor color, double max, int nbCar) {
|
||||
return progressBar(new double[] { value }, new ChatColor[] { color }, max, nbCar);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user