Folder structure for PandaLib

This commit is contained in:
2021-03-21 12:41:43 +01:00
parent 3c3ba8bca3
commit 4da86314c8
80 changed files with 1668 additions and 1668 deletions

27
Core/.classpath Normal file
View 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
View File

@@ -0,0 +1,2 @@
/target/
dependency-reduced-pom.xml

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

View 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

View 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

View File

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

69
Core/pom.xml Normal file
View 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>

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

View File

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

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

View File

@@ -0,0 +1,6 @@
package fr.pandacube.util;
@FunctionalInterface
public interface Callback<T> {
public void done(T arg);
}

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

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

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

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

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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

View 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 dune 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 "aujourdhui à " + (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 dune 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 "aujourdhui à " + (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;
}
}
}
}

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

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

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

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

View File

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

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

View 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); // cant be Byte due to MYSQL JDBC Connector limitations
public static final SQLType<Integer> BYTE = TINYINT;
public static final SQLType<Integer> SMALLINT = new SQLType<>("SMALLINT", Integer.class); // cant be Short due to MYSQL JDBC Connector limitations
public static final SQLType<Integer> SHORT = SMALLINT;
public static final SQLType<Integer> INT = new SQLType<>("INT", Integer.class);
public static final SQLType<Integer> INTEGER = INT;
public static final SQLType<Long> BIGINT = new SQLType<>("BIGINT", Long.class);
public static final SQLType<Long> LONG = BIGINT;
public static final SQLType<Date> DATE = new SQLType<>("DATE", Date.class);
public static final SQLType<Float> FLOAT = new SQLType<>("FLOAT", Float.class);
public static final SQLType<Double> DOUBLE = new SQLType<>("DOUBLE", Double.class);
@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);
}

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

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

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

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

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

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

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

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

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

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

View File

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

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

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

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

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

View File

@@ -0,0 +1,11 @@
package fr.pandacube.util.search;
import java.util.Set;
public interface SearchResult {
public Set<String> getSearchKeywords();
public Set<String> getSuggestionKeywords();
}

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

View File

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

View File

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

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

View File

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