Refactor package

This commit is contained in:
2019-10-26 23:15:49 +02:00
parent e23a19213c
commit 05d115cd19
85 changed files with 206 additions and 320 deletions

View File

@@ -0,0 +1,65 @@
package fr.pandacube.util;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
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 map.entrySet().iterator();
}
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();
}
}

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,60 @@
package fr.pandacube.util;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DateUtil {
/**
* @see {@link com.earth2me.essentials.utils.DateUtil#parseDateDiff(String, boolean)}
*/
public static long parseDateDiff(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();
}
}

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,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,159 @@
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");
// 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"));
}
public final int versionNumber;
public final List<String> versionDisplay;
private MinecraftVersion(int v, String... d) {
versionNumber = v;
versionDisplay = Arrays.asList(d);
}
@Override
public String toString() {
return StringUtil.joinGrammatically(", ", " et ", versionDisplay);
}
public static MinecraftVersion getVersion(int v) {
for (MinecraftVersion mcV : values())
if (mcV.versionNumber == v) return mcV;
return null;
}
public static String displayOptimizedListOfVersions(List<MinecraftVersion> versions) {
return StringUtil.joinGrammatically(", ", " et ", getVersionsDisplayList(versions));
}
public static final List<String> getVersionsDisplayList(List<MinecraftVersion> vList) {
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 Character(c).toString().toLowerCase())) {
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,145 @@
package fr.pandacube.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
/**
* This class performs a name lookup for a player and gets back all the name
* changes of the player (if any).
* <br/>
* <a href="https://bukkit.org/threads/player-name-history-lookup.412679/">https
* ://bukkit.org/threads/player-name-history-lookup.412679/</a>
*
* @since 25-3-2016
* @author mine-care (AKA fillpant)
*
*/
public class PlayerNameHistoryLookup {
/**
* The URL from Mojang API that provides the JSON String in response.
*/
private static final String LOOKUP_URL = "https://api.mojang.com/user/profiles/%s/names";
private static final Gson JSON_PARSER = new Gson();
/**
* <h1>NOTE: Avoid running this method <i>Synchronously</i> with the main
* thread!It blocks while attempting to get a response from Mojang servers!
* </h1>
*
* @param player The UUID of the player to be looked up.
* @return Returns an array of {@link PreviousPlayerNameEntry} objects, or
* null if the response couldn't be interpreted.
* @throws IOException {@link #getPlayerPreviousNames(String)}
*/
public static PreviousPlayerNameEntry[] getPlayerPreviousNames(UUID player) throws IOException {
return getPlayerPreviousNames(player.toString());
}
/**
* <h1>NOTE: Avoid running this method <i>Synchronously</i> with the main
* thread! It blocks while attempting to get a response from Mojang servers!
* </h1>
* Alternative method accepting an {@link OfflinePlayer} (and therefore
* {@link Player}) objects as parameter.
*
* @param uuid The UUID String to lookup
* @return Returns an array of {@link PreviousPlayerNameEntry} objects, or
* null if the response couldn't be interpreted.
* @throws IOException {@link #getRawJsonResponse(String)}
*/
public static PreviousPlayerNameEntry[] getPlayerPreviousNames(String uuid) throws IOException {
if (uuid == null || uuid.isEmpty()) return null;
uuid = uuid.replace("-", "");
String response = getRawJsonResponse(new URL(String.format(LOOKUP_URL, uuid)));
PreviousPlayerNameEntry[] names = JSON_PARSER.fromJson(response, PreviousPlayerNameEntry[].class);
return names;
}
/**
* This is a helper method used to read the response of Mojang's API
* webservers.
*
* @param u the URL to connect to
* @return a String with the data read.
* @throws IOException Inherited by {@link BufferedReader#readLine()},
* {@link BufferedReader#close()}, {@link URL},
* {@link HttpURLConnection#getInputStream()}
*/
private static String getRawJsonResponse(URL u) throws IOException {
HttpURLConnection con = (HttpURLConnection) u.openConnection();
con.setDoInput(true);
con.setConnectTimeout(2000);
con.setReadTimeout(2000);
con.connect();
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String response = in.readLine();
in.close();
return response;
}
/**
* This class represents the typical response expected by Mojang servers
* when requesting the name history of a player.
*/
public class PreviousPlayerNameEntry {
private String name;
@SerializedName("changedToAt")
private long changeTime;
/**
* Gets the player name of this entry.
*
* @return The name of the player.
*/
public String getPlayerName() {
return name;
}
/**
* Get the time of change of the name.
* <br>
* <b>Note: This will return 0 if the name is the original (initial)
* name of the player! Make sure you check if it is 0 before handling!
* <br>
* Parsing 0 to a Date will result in the date "01/01/1970".</b>
*
* @return a timestamp in miliseconds that you can turn into a date or
* handle however you want :)
*/
public long getChangeTime() {
return changeTime;
}
/**
* Check if this name is the name used to register the account (the
* initial/original name)
*
* @return a boolean, true if it is the the very first name of the
* player, otherwise false.
*/
public boolean isPlayersInitialName() {
return getChangeTime() == 0;
}
@Override
public String toString() {
return "Name: " + name + " Date of change: " + new Date(changeTime).toString();
}
}
public static void main(String[] args) throws IOException {
System.out.println(Arrays.toString(getPlayerPreviousNames("a18d9b2c-e18f-4933-9e15-36452bc36857")));
}
}

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[rand.nextInt(arr.length)];
}
public static <T> T listElement(List<T> arr) {
return 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,155 @@
package fr.pandacube.util;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
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);
}
}

View File

@@ -0,0 +1,124 @@
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 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;
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 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,32 @@
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);
}
}

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,41 @@
package fr.pandacube.util.commands;
import java.util.Arrays;
public class AbstractCommand {
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,87 @@
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.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@FunctionalInterface
public interface TabProposal {
public abstract List<String> getProposal(String token);
public static Predicate<String> filter(String token) {
return (proposal) -> proposal.toLowerCase().startsWith(token.toLowerCase());
}
public static List<String> filterStream(Stream<String> stream, String token) {
return stream.filter(filter(token)).sorted().collect(Collectors.toList());
}
public static TabProposal empty() { return t -> Collections.emptyList(); }
public static <E extends Enum<E>> TabProposal fromEnum(Class<E> enumClass) {
return fromEnumValues(enumClass.getEnumConstants());
}
@SafeVarargs
public static <E extends Enum<E>> TabProposal fromEnumValues(E... enumValues) {
return fromStream(Arrays.stream(enumValues).map(Enum::name));
}
public static TabProposal fromCollection(Collection<String> proposals) {
return fromStream(proposals.stream());
}
public static TabProposal fromIntRange(int startIncluded, int endIncluded) {
return fromStream(IntStream.rangeClosed(startIncluded, endIncluded).mapToObj(Integer::toString));
}
public static TabProposal fromStream(Stream<String> proposals) {
return t -> filterStream(proposals, t);
}
/**
* Allow tab completion to supply proposal from multi-args (arguments with space,
* generally the last argument of a command) parameters
* @param args all the arguments currently in the buffer
* @param index the index of the first argument of the multi-args parameter
* @param proposals all possible proposals for the multi-args parameter
* @return
*/
public static TabProposal withMultiArgsLastParam(String[] args, int index, Collection<String> proposals) {
String lastParamToken = AbstractCommand.getLastParams(args, index);
String[] splittedToken = lastParamToken.split(" ", -1);
int currentTokenPosition = splittedToken.length - 1;
String[] previousTokens = Arrays.copyOf(splittedToken, currentTokenPosition);
return token -> {
List<String> currentTokenProposal = new ArrayList<>();
for (String p : proposals) {
String[] splittedProposal = p.split(" ", -1);
if (splittedProposal.length <= currentTokenPosition)
continue;
if (!Arrays.equals(Arrays.copyOf(splittedToken, currentTokenPosition), previousTokens))
continue;
if (splittedProposal[currentTokenPosition].isEmpty())
continue;
if (filter(token).test(splittedProposal[currentTokenPosition]))
currentTokenProposal.add(splittedProposal[currentTokenPosition]);
}
return currentTokenProposal;
};
}
}

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 net.md_5.bungee.api.ChatColor;
/**
* 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 ChatColor.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,32 @@
package fr.pandacube.util.measurement;
import java.text.DecimalFormat;
public class MemoryUtil {
private static final DecimalFormat format = new DecimalFormat("#####0.00");
public static String humanReadableSize(long octet, boolean si) {
boolean neg = octet < 0;
double size = Math.abs(octet);
int diveBy = si ? 1000 : 1024;
if (size < diveBy) return (neg ? "-" : "") + size + "o";
size /= diveBy;
if (size < diveBy) return (neg ? "-" : "") + format.format(size) + (si ? "ko" : "kio");
size /= diveBy;
if (size < diveBy) return (neg ? "-" : "") + format.format(size) + (si ? "Mo" : "Mio");
size /= diveBy;
if (size < diveBy) return (neg ? "-" : "") + format.format(size) + (si ? "Go" : "Gio");
size /= diveBy;
return (neg ? "-" : "") + format.format(size) + (si ? "To" : "Tio");
}
public static String humanReadableSize(long octet) {
return humanReadableSize(octet, false);
}
}

View File

@@ -0,0 +1,41 @@
package fr.pandacube.util.measurement;
public class TimeUtil {
public static String durationToString(long msec_time, boolean dec_seconde) {
boolean neg = msec_time < 0;
msec_time = Math.abs(msec_time);
int j = 0, h = 0, m = 0, s = 0;
long msec = msec_time;
j = (int) (msec / (1000 * 60 * 60 * 24));
msec -= (long) (1000 * 60 * 60 * 24) * j;
h = (int) (msec / (1000 * 60 * 60));
msec -= (long) (1000 * 60 * 60) * h;
m = (int) (msec / (1000 * 60));
msec -= (long) (1000 * 60) * m;
s = (int) (msec / 1000);
msec -= (long) 1000 * s;
String result = "";
if (j > 0) result = result.concat(j + "j ");
if (h > 0) result = result.concat(h + "h ");
if (m > 0) result = result.concat(m + "m ");
if (s > 0 && !dec_seconde) result = result.concat(s + "s");
else if (dec_seconde && (s > 0 || msec > 0)) {
msec += s * 1000;
result = result.concat((msec / 1000D) + "s");
}
if (result.equals("")) result = "0";
result = result.trim();
if (neg)
result = "-" + result;
return result;
}
public static String durationToString(long msec_time) {
return durationToString(msec_time, false);
}
}

View File

@@ -0,0 +1,224 @@
package fr.pandacube.util.network.client;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.javatuples.Pair;
import fr.pandacube.Pandacube;
import fr.pandacube.util.Log;
import fr.pandacube.util.network.packet.Packet;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.PacketException;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.ResponseCallback;
import fr.pandacube.util.network.packet.packets.global.PacketD0ServerException;
public class TCPClient extends Thread implements Closeable {
private Socket socket;
private SocketAddress addr;
private TCPClientListener listener;
private InputStream in;
private OutputStream out;
private Object outSynchronizer = new Object();
private List<Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>>> callbacks = Collections.synchronizedList(new ArrayList<>());
private List<Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>>> callbacksAvoidListener = Collections.synchronizedList(new ArrayList<>());
private AtomicBoolean isClosed = new AtomicBoolean(false);
public TCPClient(InetSocketAddress a, String connName, TCPClientListener l) throws IOException {
super("TCPCl " + connName);
setDaemon(true);
if (a == null || l == null) throw new IllegalArgumentException("les arguments ne peuvent pas être null");
socket = new Socket();
socket.setReceiveBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socket.setSendBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socket.setSoTimeout(Pandacube.NETWORK_TIMEOUT);
socket.connect(a);
addr = a;
listener = l;
try {
listener.onConnect(this);
} catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onConnect()", e);
}
}
@Override
public void run() {
try {
byte[] code = new byte[1];
while (!socket.isClosed() && in.read(code) != -1) {
byte[] sizeB = new byte[4];
if (in.read(sizeB) != 4) throw new IOException("Socket " + addr + " fermé");
int size = ByteBuffer.wrap(sizeB).getInt();
byte[] content = new byte[size];
forceReadBytes(content);
byte[] packetData = ByteBuffer.allocate(1 + 4 + size).put(code).put(sizeB).put(content).array();
try {
Packet p = Packet.constructPacket(packetData);
if (!(p instanceof PacketServer))
throw new PacketException(p.getClass().getCanonicalName() + " is not a subclass of PacketServer");
if (p instanceof PacketD0ServerException) {
try {
listener.onServerException(this, ((PacketD0ServerException)p).getExceptionString());
} catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onServerException()", e);
}
}
PacketServer ps = (PacketServer) p;
boolean callbackExecuted = executeCallbacks(ps, callbacksAvoidListener);
try {
if (!callbackExecuted)
listener.onPacketReceive(this, ps);
} catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onPacketReceive()", e);
}
executeCallbacks(ps, callbacks);
} catch (Exception e) {
Log.severe("Exception while handling packet from server", e);
}
}
} catch (Exception e) {
Log.severe(e);
}
close();
}
private boolean executeCallbacks(PacketServer ps, List<Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>>> callbacks) {
boolean executedOne = false;
synchronized (callbacks) {
for(Iterator<Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>>> it = callbacks.iterator(); it.hasNext();) {
Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>> c = it.next();
try {
if (c.getValue0().test(ps)) {
it.remove();
c.getValue1().call(ps);
executedOne = true;
}
} catch (Exception e) {
Log.severe("Exception while executing callback", e);
}
}
}
return executedOne;
}
private void forceReadBytes(byte[] buff) throws IOException {
int pos = 0;
do {
int nbR = in.read(buff, pos, buff.length - pos);
if (nbR == -1) throw new IOException("Can't read required amount of byte");
pos += nbR;
} while (pos < buff.length);
}
public void send(PacketClient packet) throws IOException {
synchronized (outSynchronizer) {
out.write(packet.getFullSerializedPacket());
out.flush();
}
}
public void sendAndGetResponse(PacketClient packet, Predicate<PacketServer> responseCondition, ResponseCallback<PacketServer> callback, boolean avoidListener) throws IOException {
Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>> p = new Pair<>(responseCondition, callback);
if (avoidListener)
callbacksAvoidListener.add(p);
else
callbacks.add(p);
send(packet);
}
public PacketServer sendAndWaitForResponse(PacketClient packet, Predicate<PacketServer> responseCondition) throws IOException, InterruptedException {
AtomicReference<PacketServer> psStorage = new AtomicReference<>(null);
synchronized (psStorage) {
sendAndGetResponse(packet, responseCondition, packetServer -> {
synchronized (psStorage) {
psStorage.set(packetServer);
psStorage.notifyAll();
}
}, true);
psStorage.wait();
return psStorage.get();
}
}
@Override
public void close() {
try {
synchronized (outSynchronizer) {
if (isClosed.get()) return;
socket.close();
isClosed.set(true);
try {
listener.onDisconnect(this);
} catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onDisconnect()", e);
}
}
} catch (IOException e) {
Log.warning(e);
}
}
public void sendSilently(PacketClient packet) {
try {
send(packet);
} catch (IOException e) {}
}
public SocketAddress getServerAddress() {
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,42 @@
package fr.pandacube.util.network.client;
import fr.pandacube.util.Log;
import fr.pandacube.util.network.packet.PacketServer;
public interface TCPClientListener {
/**
* Called when a connection is opened
* @param connection the connection which is opening
*/
public void onConnect(TCPClient connection);
/**
* Called when the server send us a PacketServerException when an exception is thrown
* server side when handling our packets or eventually when the client is concerned
* with this error.<br/>
* The default implementation of this method just call the internal Logger of PandacubeUtil
* to print the stacktrace of the Exception.
* @param connection the connection which just received the error
* @param exceptionString a string representation of the exception. If the server
* use this Java library, it may be a full representation of
* {@link Throwable#printStackTrace()}.
*/
public default void onServerException(TCPClient connection, String exceptionString) {
Log.severe("Exception thrown by server through " + connection + " : \n"+exceptionString);
}
/**
* Called when the server send us a packet
* @param connection the connection where the packet come from
* @param packet the packet
*/
public void onPacketReceive(TCPClient connection, PacketServer packet);
/**
* Called before the connection closed
* @param connection the connection which is closing
*/
public void onDisconnect(TCPClient connection);
}

View File

@@ -0,0 +1,118 @@
package fr.pandacube.util.network.packet;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import fr.pandacube.Pandacube;
import fr.pandacube.util.Log;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
import fr.pandacube.util.network.packet.bytebuffer.ByteSerializable;
import fr.pandacube.util.network.packet.packets.core_slave.Packet30ClientDeclareProcess;
import fr.pandacube.util.network.packet.packets.core_slave.Packet31ClientClose;
import fr.pandacube.util.network.packet.packets.core_slave.Packet32ClientProcessQueryResponse;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB0ServerClose;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB1ServerProcessDeclarationConfirm;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB2ServerConnectSuccess;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB3ServerProcessInput;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB4ServerProcessQuery;
import fr.pandacube.util.network.packet.packets.global.Packet50ClientAuthenticate;
import fr.pandacube.util.network.packet.packets.global.Packet51ClientLogRecord;
import fr.pandacube.util.network.packet.packets.global.PacketD0ServerException;
import fr.pandacube.util.network.packet.packets.global.PacketD1ServerCantAuthenticate;
import fr.pandacube.util.network.packet.packets.web.Packet00ClientWebRequest;
import fr.pandacube.util.network.packet.packets.web.Packet80ServerWebResponse;
/** <pre>
* Identification des packets réseaux
* byte (xxxxxxxx)
* client : server :
* clt / sv (x-------) (0-------) (1-------)
* 0x00 - 0x7F 0x80 - 0xFF
* use case (-xxx----)
* - web (-000----) 0x00 - 0x0F 0x80 - 0x8F (client is Apache, server is PandacubeCore master (PandacubeWeb))
* - spigot (-001----) 0x10 - 0x1F 0x90 - 0x9F (client is PandacubeSpigot, server is PandacubeCore master)
* - bungee (-010----) 0x20 - 0x2F 0xA0 - 0xAF (client is PandacubeBungee, server is PandacubeCore master)
* -coreslave(-011----) 0x30 - 0x3F 0xB0 - 0xBF (client is PandacubeCore slave, sv is PandacubeCore master)
* - global (-101----) 0x50 - 0x5F 0xD0 - 0xDF
*
* - reserved if not enough packet id in certain use case
* (-11x----) 0x60 - 0x7F 0xE0 - 0xFF
*
* packet id (----xxxx)
* </pre>
*/
public abstract class Packet implements ByteSerializable {
private final byte code;
public Packet(byte c) {
code = c;
}
public byte getCode() {
return code;
}
public byte[] getFullSerializedPacket() {
ByteBuffer internal = new ByteBuffer(CHARSET).putObject(this);
byte[] data = Arrays.copyOfRange(internal.array(), 0, internal.getPosition());
return new ByteBuffer(5 + data.length, CHARSET).putByte(code).putInt(data.length).putByteArray(data).array();
}
public static final Charset CHARSET = Pandacube.NETWORK_CHARSET;
private static Map<Byte, Class<? extends Packet>> packetTypes = new HashMap<>();
public static Packet constructPacket(byte[] data) {
if (!packetTypes.containsKey(data[0]))
throw new PacketException("Packet identifier not recognized: 0x" + String.format("%02X", data[0])
+ ". Maybe this packet is not registered with Packet.addPacket()");
try {
Packet p = packetTypes.get(data[0]).newInstance();
ByteBuffer dataBuffer = new ByteBuffer(Arrays.copyOfRange(data, 5, data.length), CHARSET);
p.deserializeFromByteBuffer(dataBuffer);
return p;
} catch (Exception e) {
throw new PacketException("Error while constructing packet", e);
}
}
private static <T extends Packet> void addPacket(Class<T> packetClass) {
try {
Packet p = packetClass.newInstance();
packetTypes.put(p.code, packetClass);
} catch (Exception e) {
Log.severe(e);
}
}
static {
/*
* Ajout des types de packets (client + serveur)
*/
addPacket(Packet31ClientClose.class);
addPacket(Packet30ClientDeclareProcess.class);
addPacket(Packet32ClientProcessQueryResponse.class);
addPacket(PacketB0ServerClose.class);
addPacket(PacketB2ServerConnectSuccess.class);
addPacket(PacketB1ServerProcessDeclarationConfirm.class);
addPacket(PacketB3ServerProcessInput.class);
addPacket(PacketB4ServerProcessQuery.class);
addPacket(Packet50ClientAuthenticate.class);
addPacket(Packet51ClientLogRecord.class);
addPacket(PacketD1ServerCantAuthenticate.class);
addPacket(PacketD0ServerException.class);
addPacket(Packet00ClientWebRequest.class);
addPacket(Packet80ServerWebResponse.class);
}
}

View File

@@ -0,0 +1,13 @@
package fr.pandacube.util.network.packet;
/**
* On attend d'un instance de {@link PacketClient} qu'il soit envoyé depuis
* une connexion Client vers une connexion Serveur (d'un point de vue TCP)
*/
public abstract class PacketClient extends Packet {
public PacketClient(byte c) {
super(c);
}
}

View File

@@ -0,0 +1,24 @@
package fr.pandacube.util.network.packet;
/**
*
* Thrown when there is a problem when constructing, sending or handling a packet.
*
* Only the server may send a string representation of an exception to the client, not the reverse
*
*/
public class PacketException extends RuntimeException {
private static final long serialVersionUID = 1L;
public PacketException(String m) {
super(m);
}
public PacketException(String m, Throwable t) {
super(m, t);
}
public PacketException(Throwable t) {
super(t);
}
}

View File

@@ -0,0 +1,13 @@
package fr.pandacube.util.network.packet;
/**
* On attend d'un instance de {@link PacketServer} qu'il soit envoyé depuis
* une connexion Serveur vers une connexion Client (d'un point de vue TCP)
*/
public abstract class PacketServer extends Packet {
public PacketServer(byte c) {
super(c);
}
}

View File

@@ -0,0 +1,8 @@
package fr.pandacube.util.network.packet;
@FunctionalInterface
public interface ResponseCallback<T extends Packet> {
public void call(T packet);
}

View File

@@ -0,0 +1,77 @@
package fr.pandacube.util.network.packet.bytebuffer;
import java.util.Arrays;
public class Array8Bit implements ByteSerializable {
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;
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
fromByte(buffer.getByte());
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putByte(toByte());
}
}

View File

@@ -0,0 +1,324 @@
package fr.pandacube.util.network.packet.bytebuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ByteBuffer implements Cloneable {
private java.nio.ByteBuffer buff;
private Charset charset;
public ByteBuffer(Charset c) {
this(16, c);
}
public ByteBuffer(int initSize, Charset c) {
buff = java.nio.ByteBuffer.allocate(initSize);
charset = c;
}
public ByteBuffer(byte[] data, Charset c) {
buff = java.nio.ByteBuffer.wrap(Arrays.copyOf(data, data.length));
charset = c;
}
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;
}
}
@Override
public ByteBuffer clone() {
return new ByteBuffer(Arrays.copyOf(buff.array(), buff.array().length), charset);
}
/**
* @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(charset));
}
/**
* returned string can be null
* @return
*/
public String getString() {
byte[] binaryString = getSizedByteArray();
return (binaryString == null) ? null : new String(binaryString, charset);
}
/**
* The objet will be serialized and the data put in the current buffer
*
* @param obj the object to serialize. Can't be null.
* @return the current buffer
*/
public ByteBuffer putObject(ByteSerializable obj) {
obj.serializeToByteBuffer(this);
return this;
}
/**
* Ask to object passed as argument to deserialize data in buffer and fill
* the object content. ByteSerializable object are never null.
*
* @param <T>
* @param clazz the class wich will be instanciated with his no-argument Constructor
* before filled by using {@link ByteSerializable#deserializeFromByteBuffer(ByteBuffer)}
* @return obj a reference to the filled object
*/
public <T extends ByteSerializable> T getObject(Class<T> clazz) {
try {
T obj = clazz.newInstance();
obj.deserializeFromByteBuffer(this);
return obj;
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("A ByteSerializable must have a no-argument Constructor", e);
}
}
/**
*
* @param list The list itself can be null, but not the values.
* @return
*/
public ByteBuffer putListObject(List<ByteSerializable> list) {
if (list.stream().anyMatch(e -> e == null))
throw new IllegalArgumentException("List of object can't contains any null value");
putInt(list.size());
for (ByteSerializable obj : list)
putObject(obj);
return this;
}
/**
*
* @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;
}
/**
*
* @param clazz
* @return Can be null. If not, there is no null element inside.
*/
public <T extends ByteSerializable> List<T> getListObject(Class<T> clazz) {
int size = getInt();
if (size < 0)
return null;
List<T> list = new ArrayList<>();
for (int i = 0; i < size; i++)
list.add(getObject(clazz));
return list;
}
/**
* @return a List of String. The list can be null, and any element can be null too.
*/
public <T extends ByteSerializable> 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,19 @@
package fr.pandacube.util.network.packet.bytebuffer;
/**
* Cette interface permet à un {@link ByteBuffer} de sérialiser sous forme de
* données binaire
* les attributs de la classe courante.<br/>
* <br/>
* Les classes concrètes implémentant cette interface doivent avoir un
* constructeur vide, utilisé
* lors de la désérialisation
*
*/
public interface ByteSerializable {
public void serializeToByteBuffer(ByteBuffer buffer);
public void deserializeFromByteBuffer(ByteBuffer buffer);
}

View File

@@ -0,0 +1,39 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet30ClientDeclareProcess extends PacketClient {
private String processName;
private String type;
public Packet30ClientDeclareProcess() {
super((byte)0x30);
}
public void setProcessName(String pN) {
processName = pN;
}
public void setType(String t) {
type = t;
}
public String getProcessName() { return processName; }
public String getType() { return type; }
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(processName);
buffer.putString(type);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
processName = buffer.getString();
type = buffer.getString();
}
}

View File

@@ -0,0 +1,22 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet31ClientClose extends PacketClient {
public Packet31ClientClose() {
super((byte)0x31);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
// no data
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
// no data
}
}

View File

@@ -0,0 +1,61 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB4ServerProcessQuery.QueryType;
public class Packet32ClientProcessQueryResponse extends PacketClient {
private QueryType type;
private int queryId;
private byte[] responseData = null;
public Packet32ClientProcessQueryResponse() {
super((byte)0x32);
}
public QueryType getType() { return type; }
public int getQueryId() { return queryId; }
public byte[] getResponseData() { return responseData; }
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putInt(type.ordinal());
buffer.putInt(queryId);
buffer.putSizedByteArray(responseData);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
type = QueryType.values()[buffer.getInt()];
queryId = buffer.getInt();
responseData = buffer.getSizedByteArray();
}
public static Packet32ClientProcessQueryResponse destroyResponse(int queryId) {
Packet32ClientProcessQueryResponse q = new Packet32ClientProcessQueryResponse();
q.type = QueryType.DESTROY;
q.queryId = queryId;
return q;
}
public static Packet32ClientProcessQueryResponse isAliveResponse(int queryId, boolean resp) {
Packet32ClientProcessQueryResponse q = new Packet32ClientProcessQueryResponse();
q.type = QueryType.IS_ALIVE;
q.queryId = queryId;
q.responseData = new byte[] {(byte)(resp ? 1 : 0)};
return q;
}
public static Packet32ClientProcessQueryResponse exitStatusResponse(int queryId, int resp) {
Packet32ClientProcessQueryResponse q = new Packet32ClientProcessQueryResponse();
q.type = QueryType.EXIT_STATUS;
q.queryId = queryId;
q.responseData = new ByteBuffer(4, CHARSET).putInt(resp).array();
return q;
}
}

View File

@@ -0,0 +1,22 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketB0ServerClose extends PacketServer {
public PacketB0ServerClose() {
super((byte)0xB0);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
// no data
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
// no data
}
}

View File

@@ -0,0 +1,32 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketB1ServerProcessDeclarationConfirm extends PacketServer {
private String serverName;
public PacketB1ServerProcessDeclarationConfirm() {
super((byte)0xB1);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(serverName);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
serverName = buffer.getString();
}
public String getServerName() {
return serverName;
}
public void setProcessName(String name) {
serverName = name;
}
}

View File

@@ -0,0 +1,22 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketB2ServerConnectSuccess extends PacketServer {
public PacketB2ServerConnectSuccess() {
super((byte)0xB2);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
// no data
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
// no data
}
}

View File

@@ -0,0 +1,42 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketB3ServerProcessInput extends PacketServer {
private String serverName;
private byte[] dataToSend;
public PacketB3ServerProcessInput() {
super((byte)0xB3);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(serverName);
buffer.putSizedByteArray(dataToSend);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
serverName = buffer.getString();
dataToSend = buffer.getSizedByteArray();
}
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public byte[] getDataToSend() {
return dataToSend;
}
public void setDataToSend(byte[] dataToSend) {
this.dataToSend = dataToSend;
}
}

View File

@@ -0,0 +1,73 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.RandomUtil;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketB4ServerProcessQuery extends PacketServer {
private String processName;
private QueryType type;
private int queryId = RandomUtil.rand.nextInt();
private byte[] queryData = null;
public PacketB4ServerProcessQuery() {
super((byte)0xB4);
}
public String getProcessName() { return processName; }
public QueryType getType() { return type; }
public int getQueryId() { return queryId; }
public byte[] getQueryData() { return queryData; }
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putInt(type.ordinal());
buffer.putInt(queryId);
buffer.putSizedByteArray(queryData);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
type = QueryType.values()[buffer.getInt()];
queryId = buffer.getInt();
queryData = buffer.getSizedByteArray();
}
public static PacketB4ServerProcessQuery startQuery(String processName) {
PacketB4ServerProcessQuery q = new PacketB4ServerProcessQuery();
q.processName = processName;
q.type = QueryType.START;
return q;
}
public static PacketB4ServerProcessQuery destroyQuery(String processName, boolean wait) {
PacketB4ServerProcessQuery q = new PacketB4ServerProcessQuery();
q.processName = processName;
q.type = QueryType.DESTROY;
q.queryData = new byte[] {(byte)(wait ? 1 : 0)};
return q;
}
public static PacketB4ServerProcessQuery isAliveQuery(String processName) {
PacketB4ServerProcessQuery q = new PacketB4ServerProcessQuery();
q.processName = processName;
q.type = QueryType.IS_ALIVE;
return q;
}
public static PacketB4ServerProcessQuery exitStatusQuery(String processName) {
PacketB4ServerProcessQuery q = new PacketB4ServerProcessQuery();
q.processName = processName;
q.type = QueryType.EXIT_STATUS;
return q;
}
public enum QueryType {
START, DESTROY, IS_ALIVE, EXIT_STATUS;
}
}

View File

@@ -0,0 +1,35 @@
package fr.pandacube.util.network.packet.packets.global;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet50ClientAuthenticate extends PacketClient {
private String password;
private String additionalData = "";
public Packet50ClientAuthenticate() {
super((byte)0x50);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(password);
buffer.putString(additionalData);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
password = buffer.getString();
additionalData = buffer.getString();
}
public String getPassword() { return password; }
public void setPassword(String p) { password = p; }
public String getAdditionalData() { return additionalData; }
public void setAdditionalData(String data) { additionalData = data; }
}

View File

@@ -0,0 +1,76 @@
package fr.pandacube.util.network.packet.packets.global;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet51ClientLogRecord extends PacketClient {
private long time;
private String level;
private String threadName;
private String message;
private String throwable;
public Packet51ClientLogRecord() {
super((byte)0x51);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
time = buffer.getLong();
level = buffer.getString();
threadName = buffer.getString();
message = buffer.getString();
throwable = buffer.getString();
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putLong(time);
buffer.putString(level);
buffer.putString(threadName);
buffer.putString(message);
buffer.putString(throwable);
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getThrowable() {
return throwable;
}
public void setThrowable(String throwable) {
this.throwable = throwable;
}
}

View File

@@ -0,0 +1,38 @@
package fr.pandacube.util.network.packet.packets.global;
import java.io.PrintWriter;
import java.io.StringWriter;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketD0ServerException extends PacketServer {
private String exception;
public PacketD0ServerException() {
super((byte)0xD0);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(exception);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
exception = buffer.getString();
}
public void setException(Throwable t) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
exception = sw.toString();
}
public String getExceptionString() {
return exception;
}
}

View File

@@ -0,0 +1,20 @@
package fr.pandacube.util.network.packet.packets.global;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketD1ServerCantAuthenticate extends PacketServer {
public PacketD1ServerCantAuthenticate() {
super((byte)0xD1);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
}
}

View File

@@ -0,0 +1,30 @@
package fr.pandacube.util.network.packet.packets.global;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketD2ServerCommand extends PacketServer {
private String command;
private boolean async;
private boolean returnResult;
public PacketD2ServerCommand() {
super((byte)0xD2);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(command);
buffer.putByte((byte) (async ? 1 : 0));
buffer.putByte((byte) (returnResult ? 1 : 0));
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
command = buffer.getString();
async = buffer.getByte() != 0;
returnResult = buffer.getByte() != 0;
}
}

View File

@@ -0,0 +1,43 @@
package fr.pandacube.util.network.packet.packets.web;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet00ClientWebRequest extends PacketClient {
private String password;
private String jsonData;
public Packet00ClientWebRequest() {
super((byte)0x00);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(password);
buffer.putString(jsonData);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
password = buffer.getString();
jsonData = buffer.getString();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getJsonData() {
return jsonData;
}
public void setJsonData(String jsonData) {
this.jsonData = jsonData;
}
}

View File

@@ -0,0 +1,32 @@
package fr.pandacube.util.network.packet.packets.web;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet80ServerWebResponse extends PacketServer {
private String jsonData;
public Packet80ServerWebResponse() {
super((byte)0x80);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(jsonData);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
jsonData = buffer.getString();
}
public String getJsonData() {
return jsonData;
}
public void setJsonData(String jsonData) {
this.jsonData = jsonData;
}
}

View File

@@ -0,0 +1,56 @@
package fr.pandacube.util.network.server;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import fr.pandacube.util.network.server.TCPServer.TCPServerClientConnection;
public class BandwidthCalculation {
private List<PacketStat> packetHistory = new LinkedList<>();
public synchronized void addPacket(TCPServerClientConnection co, boolean in, long size) {
packetHistory.add(new PacketStat(co, in, size));
}
/**
* Get the instant bandwith in byte/s
*
* @param input true if getting input bw, false if getting output, null if
* getting input + output
* @param co
* @return
*/
public synchronized long getBandWidth(Boolean input, TCPServerClientConnection co) {
long currentTime = System.currentTimeMillis();
Iterator<PacketStat> it = packetHistory.iterator();
long sum = 0;
while (it.hasNext()) {
PacketStat el = it.next();
if (el.time < currentTime - 1000) {
it.remove();
continue;
}
if (input != null && el.input != input.booleanValue()) continue;
if (co != null && !co.equals(el.connection)) continue;
sum += el.packetSize;
}
return sum;
}
private class PacketStat {
public final long time;
public final long packetSize;
public final boolean input;
public final TCPServerClientConnection connection;
public PacketStat(TCPServerClientConnection co, boolean input, long size) {
time = System.currentTimeMillis();
packetSize = size;
this.input = input;
connection = co;
}
}
}

View File

@@ -0,0 +1,355 @@
package fr.pandacube.util.network.server;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.logging.Level;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.javatuples.Pair;
import fr.pandacube.Pandacube;
import fr.pandacube.util.Log;
import fr.pandacube.util.network.packet.Packet;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.PacketException;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.ResponseCallback;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
import fr.pandacube.util.network.packet.packets.global.PacketD0ServerException;
/**
*
* @author Marc Baloup
*
*/
public class TCPServer extends Thread implements Closeable {
private static AtomicInteger connectionCounterId = new AtomicInteger(0);
private ServerSocket socket;
private TCPServerListener listener;
private String socketName;
private List<TCPServerClientConnection> clients = Collections.synchronizedList(new ArrayList<>());
private AtomicBoolean isClosed = new AtomicBoolean(false);
public final BandwidthCalculation bandwidthCalculation = new BandwidthCalculation();
public TCPServer(int port, String sckName, TCPServerListener l) throws IOException {
super("TCPSv " + sckName);
setDaemon(true);
if (port <= 0 || port > 65535) throw new IllegalArgumentException("le numéro de port est invalide");
socket = new ServerSocket();
socket.setReceiveBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socket.setPerformancePreferences(0, 1, 0);
socket.bind(new InetSocketAddress(port));
listener = l;
try {
listener.onSocketOpen(this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onSocketOpen()", e);
}
socketName = sckName;
}
@Override
public void run() {
try {
while (true) {
Socket socketClient = socket.accept();
socketClient.setSendBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socketClient.setSoTimeout(Pandacube.NETWORK_TIMEOUT);
try {
TCPServerClientConnection co = new TCPServerClientConnection(socketClient,
connectionCounterId.getAndIncrement());
clients.add(co);
co.start();
} catch (IOException e) {
Log.getLogger().log(Level.SEVERE, "Connexion impossible avec " + socketClient.getInetAddress());
}
}
} catch(SocketException e) {
} catch (Exception e) {
Log.warning("Plus aucune connexion ne peux être acceptée", e);
}
}
public class TCPServerClientConnection extends Thread {
private Socket cSocket;
private InputStream in;
private OutputStream out;
private SocketAddress address;
private TCPServerConnectionOutputThread outThread;
private List<Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>>> callbacks = Collections.synchronizedList(new ArrayList<>());
private List<Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>>> callbacksAvoidListener = Collections.synchronizedList(new ArrayList<>());
public TCPServerClientConnection(Socket s, int coId) throws IOException {
super("TCPSv " + socketName + " Conn#" + coId + " In");
setDaemon(true);
cSocket = s;
in = cSocket.getInputStream();
out = cSocket.getOutputStream();
address = new InetSocketAddress(cSocket.getInetAddress(), cSocket.getPort());
try {
listener.onClientConnect(TCPServer.this, this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onClientConnect()", e);
}
outThread = new TCPServerConnectionOutputThread(coId);
outThread.start();
}
@Override
public void run() {
try {
byte[] code = new byte[1];
while (!cSocket.isClosed() && in.read(code) != -1) {
byte[] sizeB = new byte[4];
if (in.read(sizeB) != 4) throw new IOException("Socket " + address + " closed");
int size = new ByteBuffer(sizeB, Packet.CHARSET).getInt();
byte[] content = new byte[size];
forceReadBytes(content);
byte[] packetData = new ByteBuffer(1 + 4 + size, Packet.CHARSET).putByteArray(code).putByteArray(sizeB)
.putByteArray(content).array();
bandwidthCalculation.addPacket(this, true, packetData.length);
try {
Packet p = Packet.constructPacket(packetData);
if (!(p instanceof PacketClient))
throw new PacketException(p.getClass().getCanonicalName() + " is not an instanceof PacketClient");
PacketClient pc = (PacketClient) p;
boolean oneCallbackExecuted = executeCallbacks(pc, callbacksAvoidListener);
if (!oneCallbackExecuted)
listener.onPacketReceive(TCPServer.this, this, pc);
executeCallbacks(pc, callbacks);
} catch (Throwable e) {
Log.severe("Exception while handling packet. This exception will be sent to the client with PacketServerException packet.", e);
PacketD0ServerException packet = new PacketD0ServerException();
packet.setException(e);
send(packet);
if (e instanceof InterruptedException || e instanceof Error)
throw e;
}
}
} catch(SocketException e) {
} catch (Exception e) {
Log.severe("Closing connection " + address, e);
}
close();
}
private boolean executeCallbacks(PacketClient pc, List<Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>>> callbacks) {
boolean executedOne = false;
synchronized (callbacks) {
for(Iterator<Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>>> it = callbacks.iterator(); it.hasNext();) {
Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>> c = it.next();
try {
if (c.getValue0().test(pc)) {
it.remove();
c.getValue1().call(pc);
executedOne = true;
}
} catch (Exception e) {
Log.severe("Exception while executing callback", e);
}
}
}
return executedOne;
}
public void send(PacketServer p) {
outThread.addPacket(p);
}
public void sendAndGetResponse(PacketServer packet, Predicate<PacketClient> responseCondition, ResponseCallback<PacketClient> callback, boolean avoidListener) {
Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>> p = new Pair<>(responseCondition, callback);
if (avoidListener)
callbacksAvoidListener.add(p);
else
callbacks.add(p);
send(packet);
}
/**
*
* @param packet the packet to send
* @param responseCondition {@link Predicate} that check each received packet to know which
* is the expected one as a response of the sended packet.
* @param avoidListener
* @param timeout
* @return
* @throws InterruptedException
*/
public PacketClient sendAndWaitForResponse(PacketServer packet, Predicate<PacketClient> responseCondition, boolean avoidListener, long timeout) throws InterruptedException {
AtomicReference<PacketClient> pcStorage = new AtomicReference<>(null);
synchronized (pcStorage) {
sendAndGetResponse(packet, responseCondition, packetClient -> {
synchronized (pcStorage) {
pcStorage.set(packetClient);
pcStorage.notifyAll();
}
}, true);
pcStorage.wait(timeout);
return pcStorage.get();
}
}
private void forceReadBytes(byte[] buff) throws IOException {
int pos = 0;
do {
int nbR = in.read(buff, pos, buff.length - pos);
if (nbR == -1) throw new IOException("Can't read required amount of byte");
pos += nbR;
} while (pos < buff.length);
}
public void close() {
if (cSocket.isClosed()) return;
try {
listener.onClientDisconnect(TCPServer.this, this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onClientDisconnect()", e);
}
clients.remove(this);
try {
Thread.sleep(200);
cSocket.close();
if (!Thread.currentThread().equals(outThread)) send(new PacketServer((byte) 0) {
@Override public void serializeToByteBuffer(ByteBuffer buffer) {}
@Override public void deserializeFromByteBuffer(ByteBuffer buffer) {}
});
// provoque une exception dans le thread de sortie, et la
// termine
} catch (Exception e) { }
}
private class TCPServerConnectionOutputThread extends Thread {
private BlockingQueue<PacketServer> packetQueue = new LinkedBlockingDeque<>();
public TCPServerConnectionOutputThread(int coId) {
super("TCPSv " + socketName + " Conn#" + coId + " Out");
setDaemon(true);
}
private void addPacket(PacketServer packet) {
packetQueue.add(packet);
}
@Override
public void run() {
try {
while (!cSocket.isClosed()) {
PacketServer packet = packetQueue.poll(1, TimeUnit.SECONDS);
byte[] data;
if (packet != null) {
try {
data = packet.getFullSerializedPacket();
bandwidthCalculation.addPacket(TCPServerClientConnection.this, false, data.length);
out.write(data);
out.flush();
} catch (IOException e) {
throw e;
} catch (Exception e) {
Log.severe("Can't send packet "+packet.getClass(), e);
}
}
}
} catch (InterruptedException|IOException e) {}
close();
}
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("thread", getName())
.append("socket", cSocket).toString();
}
}
@Override
public void close() {
try {
if (isClosed.get()) return;
isClosed.set(true);
clients.forEach(el -> el.close());
try {
listener.onSocketClose(this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onSocketClose()", e);
}
socket.close();
} catch (IOException e) {}
}
public boolean isClosed() {
return isClosed.get() || socket.isClosed();
}
public List<TCPServerClientConnection> 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,19 @@
package fr.pandacube.util.network.server;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.server.TCPServer.TCPServerClientConnection;
public interface TCPServerListener {
public void onSocketOpen(TCPServer svConnection);
public void onClientConnect(TCPServer svConnection, TCPServerClientConnection clientConnection);
public void onPacketReceive(TCPServer svConnection, TCPServerClientConnection clientConnection,
PacketClient packet);
public void onClientDisconnect(TCPServer svConnection, TCPServerClientConnection clientConnection);
public void onSocketClose(TCPServer svConnection);
}

View File

@@ -0,0 +1,29 @@
package fr.pandacube.util.network_api.client;
import java.io.PrintStream;
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,25 @@
package fr.pandacube.util.network_api.client;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.Socket;
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,53 @@
package fr.pandacube.util.network_api.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
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();
good = line.equalsIgnoreCase("OK");
// lecture de la deuxième ligne
line = in.readLine();
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,43 @@
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;
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,14 @@
package fr.pandacube.util.network_api.server;
/**
* Interface permettant de gérer l'exécution asynchrone d'un PacketExecutor.
*
* @author Marc Baloup
*
*/
@FunctionalInterface
public interface NAPIExecutionHandler {
public void handleRun(Runnable executor);
}

View File

@@ -0,0 +1,89 @@
package fr.pandacube.util.network_api.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.Arrays;
import java.util.HashMap;
public class NetworkAPIListener implements Runnable {
private int port = 0;
String pass;
private ServerSocket serverSocket;
private HashMap<String, AbstractRequestExecutor> requestExecutors = new HashMap<>();
private String name;
private NAPIExecutionHandler nAPIExecutionHandler;
/**
* 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, NAPIExecutionHandler peh) {
port = p;
pass = pa;
name = n;
nAPIExecutionHandler = peh;
}
@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()) {
nAPIExecutionHandler.handleRun(new PacketExecutor(serverSocket.accept(), this));
}
} 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,64 @@
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
*
*/
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,70 @@
package fr.pandacube.util.network_api.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
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,29 @@
package fr.pandacube.util.network_api.server;
import java.io.PrintStream;
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,12 @@
package fr.pandacube.util.network_api.server;
public class ThreadNAPIExecutionHandler implements NAPIExecutionHandler {
@Override
public void handleRun(Runnable executor) {
Thread t = new Thread(executor);
t.setDaemon(true);
t.start();
}
}

View File

@@ -0,0 +1,77 @@
package fr.pandacube.util.orm;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBConnection {
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"
+ "&characterEncoding=utf8"
+ "&characterSetResults=utf8"
+ "&character_set_server=utf8mb4"
+ "&character_set_connection=utf8mb4";
login = l;
pass = p;
connect();
}
private void checkConnection() throws SQLException {
if (!isConnected()) {
close();
connect();
}
}
public boolean isConnected()
{
boolean connected = false;
try (ResultSet rs = conn.createStatement().executeQuery("SELECT 1;"))
{
if (rs == null)
connected = false;
else if (rs.next())
connected = true;
} catch (Exception e) {
connected = false;
}
return connected;
}
public Connection getNativeConnection() throws SQLException {
if (conn.isClosed())
connect();
long now = System.currentTimeMillis();
if (timeOfLastCheck + 5000 > now) {
timeOfLastCheck = now;
if (!conn.isValid(1))
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,364 @@
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.Collection;
import java.util.List;
import java.util.function.Consumer;
import org.javatuples.Pair;
import fr.pandacube.util.Log;
import fr.pandacube.util.orm.SQLWhereChain.SQLBoolOp;
import fr.pandacube.util.orm.SQLWhereComp.SQLComparator;
/**
* <b>ORM = Object-Relational Mapping</b>
*
* @author Marc Baloup
*
*/
public final class ORM {
private static List<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
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.info("[ORM] Start Init SQL table "+elemClass.getSimpleName());
E instance = elemClass.newInstance();
String tableName = instance.tableName();
if (!tableExist(tableName)) createTable(instance);
Log.info("[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();
}
}
private static boolean tableExist(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, Collection<Integer> ids)
throws ORMException {
return getByIds(elemClass, ids.toArray(new Integer[ids.size()]));
}
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Integer... ids) throws ORMException {
SQLField<E, Integer> idField = getSQLIdField(elemClass);
SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR);
for (Integer id : ids)
if (id != null) where.add(new SQLWhereComp(idField, SQLComparator.EQ, id));
return getAll(elemClass, where, new SQLOrderBy().add(idField), 1, null);
}
public static <E extends SQLElement<E>> E getById(Class<E> elemClass, int id) throws ORMException {
return getFirst(elemClass, new SQLWhereComp(getSQLIdField(elemClass), SQLComparator.EQ, id));
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere where)
throws ORMException {
return getFirst(elemClass, where, null, null);
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere where, SQLOrderBy orderBy)
throws ORMException {
return getFirst(elemClass, where, orderBy, null);
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere where, SQLOrderBy 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 where) throws ORMException {
return getAll(elemClass, where, null, null, null);
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere where,
SQLOrderBy orderBy) throws ORMException {
return getAll(elemClass, where, orderBy, null, null);
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere where,
SQLOrderBy 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 where,
SQLOrderBy 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 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 where,
SQLOrderBy 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 where,
SQLOrderBy 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 where,
SQLOrderBy orderBy, Integer limit, Integer offset, Consumer<E> action) throws ORMException {
initTable(elemClass);
try {
String sql = "SELECT * FROM " + elemClass.newInstance().tableName();
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 (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());
try (ResultSet set = ps.executeQuery()) {
while (set.next()) {
E elm = getElementInstance(set, elemClass);
action.accept(elm);
}
}
}
} catch (ReflectiveOperationException | 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 where) throws ORMException {
initTable(elemClass);
try {
String sql = "SELECT COUNT(*) as count FROM " + elemClass.newInstance().tableName();
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 (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());
try (ResultSet set = ps.executeQuery()) {
while (set.next()) {
return set.getLong(1);
}
}
}
} catch (ReflectiveOperationException | SQLException e) {
throw new ORMException(e);
}
throw new ORMException("Cant retrieve element count from database (The ResultSet may be empty)");
}
public static <E extends SQLElement<E>> boolean truncateTable(Class<E> elemClass) throws ORMException {
boolean success;
try (Statement stmt = connection.getNativeConnection().createStatement()) {
success = stmt.execute("TRUNCATE `" + elemClass.newInstance().tableName() + "`");
} catch(SQLException | ReflectiveOperationException e) {
throw new ORMException(e);
}
return success;
}
public static ResultSet getCustomResult(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);
}
}
@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
/*
* public static void main(String[] args) throws Throwable {
* ORM.init(new DBConnection("localhost", 3306, "pandacube", "pandacube",
* "pandacube"));
* List<SQLPlayer> players = ORM.getAll(SQLPlayer.class,
* new SQLWhereChain(SQLBoolOp.AND)
* .add(new SQLWhereNull(SQLPlayer.banTimeout, true))
* .add(new SQLWhereChain(SQLBoolOp.OR)
* .add(new SQLWhereComp(SQLPlayer.bambou, SQLComparator.EQ, 0L))
* .add(new SQLWhereComp(SQLPlayer.grade, SQLComparator.EQ, "default"))
* ),
* new SQLOrderBy().addField(SQLPlayer.playerDisplayName), null, null);
* for(SQLPlayer p : players) {
* System.out.println(p.get(SQLPlayer.playerDisplayName));
* }
* // TODO mise à jour relative d'un champ (incrément / décrément)
* }
*/
}

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,32 @@
package fr.pandacube.util.orm;
import java.util.function.Function;
/**
*
* @author Marc
*
* @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;
protected SQLCustomType(SQLType<IT> type, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) {
this(type.sqlDeclaration, type.getJavaType(), javaT, dbToJava, javaToDb);
}
protected 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;
}
// TODO tester en local
}

View File

@@ -0,0 +1,427 @@
package fr.pandacube.util.orm;
import java.lang.reflect.Modifier;
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 org.apache.commons.lang.builder.ToStringBuilder;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import fr.pandacube.util.Log;
import fr.pandacube.util.orm.SQLWhereComp.SQLComparator;
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 String tableName;
private final SQLFieldMap<E> fields;
private final Map<SQLField<E, ?>, Object> values;
/* package */ final Set<String> modifiedSinceLastSave;
@SuppressWarnings("unchecked")
public SQLElement() {
tableName = tableName();
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<>(SQLType.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);
}
public <T> void set(SQLField<E, T> field, T value) {
set(field, value, true);
}
/* 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(),
new SQLWhereComp(field.getPrimaryField(), SQLComparator.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 orderBy, Integer limit, Integer offset) throws ORMException {
T value = get(field.getPrimaryField());
if (value == null) return new SQLElementList<>();
return ORM.getAll(field.getSQLElementType(),
new SQLWhereComp(field, SQLComparator.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 void 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());
String toStringStatement = "";
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;
String sql = "";
List<Object> psValues = new ArrayList<>();
for (Map.Entry<SQLField<E, ?>, Object> entry : modifiedValues.entrySet()) {
sql += "`" + entry.getKey().getName() + "` = ? ,";
addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue());
}
if (sql.length() > 0) sql = sql.substring(0, sql.length() - 1);
try (PreparedStatement ps = db.getNativeConnection()
.prepareStatement("UPDATE " + tableName + " SET " + sql + " WHERE id=" + id)) {
int i = 1;
for (Object val : psValues)
ps.setObject(i++, val);
toStringStatement = ps.toString();
ps.executeUpdate();
}
}
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);
toStringStatement = ps.toString();
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 executing SQL statement " + toStringStatement, e);
}
Log.debug(toStringStatement);
}
@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;
}
}

View File

@@ -0,0 +1,238 @@
package fr.pandacube.util.orm;
import java.sql.PreparedStatement;
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;
import fr.pandacube.util.orm.SQLWhereChain.SQLBoolOp;
import fr.pandacube.util.orm.SQLWhereComp.SQLComparator;
/**
*
* @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.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 void saveCommon() throws ORMException {
List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return;
String sqlSet = "";
List<Object> psValues = new ArrayList<>();
for (Map.Entry<SQLField<E, ?>, Object> entry : modifiedValues.entrySet()) {
sqlSet += "`" + entry.getKey().getName() + "` = ? ,";
SQLElement.addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue());
}
if (sqlSet.length() > 0) sqlSet = sqlSet.substring(0, sqlSet.length() - 1);
String sqlWhere = "";
boolean first = true;
for (E el : storedEl) {
if (!first) sqlWhere += " OR ";
first = false;
sqlWhere += "id = " + el.getId();
}
try(PreparedStatement ps = ORM.getConnection().getNativeConnection()
.prepareStatement("UPDATE " + storedEl.get(0).tableName() + " SET " + sqlSet + " WHERE " + sqlWhere)) {
int i = 1;
for (Object val : psValues)
ps.setObject(i++, val);
Log.debug(ps.toString());
ps.executeUpdate();
applyNewValuesToElements(storedEl);
} catch (SQLException e) {
throw new ORMException(e);
}
}
@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<>()));
}
public synchronized void removeFromDB() {
List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return;
try {
String sqlWhere = "";
boolean first = true;
for (E el : storedEl) {
if (!first) sqlWhere += " OR ";
first = false;
sqlWhere += "id = " + el.getId();
}
try (PreparedStatement st = ORM.getConnection().getNativeConnection()
.prepareStatement("DELETE FROM " + storedEl.get(0).tableName() + " WHERE " + sqlWhere)) {
Log.debug(st.toString());
st.executeUpdate();
for (E el : storedEl)
el.markAsNotStored();
}
} catch (SQLException e) {
Log.severe(e);
}
}
public <T, P extends SQLElement<P>> SQLElementList<P> getReferencedEntries(SQLFKField<E, T, P> foreignKey, SQLOrderBy 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<>();
}
SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR);
values.forEach(v -> where.add(new SQLWhereComp(foreignKey.getPrimaryField(), SQLComparator.EQ, v)));
return ORM.getAll(foreignKey.getForeignElementClass(), where, 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 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<>();
}
SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR);
values.forEach(v -> where.add(new SQLWhereComp(foreignKey, SQLComparator.EQ, v)));
return ORM.getAll(foreignKey.getSQLElementType(), where, orderBy, limit, offset);
}
public <T, F extends SQLElement<F>> Map<T, SQLElementList<F>> getReferencingForeignEntriesInGroups(SQLFKField<F, T, E> foreignKey, SQLOrderBy 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);
}
public static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Class<F> fkEl) {
return idFK(nul, null, fkEl);
}
public 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;
}
}
public 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);
}
public 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,87 @@
package fr.pandacube.util.orm;
import java.util.ArrayList;
import java.util.List;
import org.javatuples.Pair;
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;
public SQLField(SQLType<T> t, boolean nul, boolean autoIncr, T deflt) {
type = t;
canBeNull = nul;
autoIncrement = autoIncr;
defaultValue = deflt;
}
public SQLField(SQLType<T> t, boolean nul) {
this(t, nul, false, null);
}
public SQLField(SQLType<T> t, boolean nul, boolean autoIncr) {
this(t, nul, autoIncr, null);
}
public 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();
}
}

View File

@@ -0,0 +1,68 @@
package fr.pandacube.util.orm;
import java.util.ArrayList;
import java.util.List;
public class SQLOrderBy {
private List<OBField> orderByFields = new ArrayList<>();
/**
* Construit une nouvelle clause ORDER BY
*/
public 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)
*/
public SQLOrderBy add(SQLField<?, ?> field, Direction d) {
orderByFields.add(new OBField(field, d));
return this;
}
/**
* Ajoute un champ dans la clause ORDER BY en construction,
* avec comme ordre de tri croissant ASC par défaut
*
* @param field le champ SQL à ordonner dans l'ordre croissant ASC
* @return l'objet courant (permet de chainer les ajouts de champs)
*/
public SQLOrderBy add(SQLField<?, ?> field) {
return add(field, Direction.ASC);
}
/* 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<?, ?> field;
public final Direction direction;
public OBField(SQLField<?, ?> f, Direction d) {
field = f;
direction = d;
}
}
public enum Direction {
ASC, DESC;
}
}

View File

@@ -0,0 +1,95 @@
package fr.pandacube.util.orm;
import java.sql.Date;
import java.util.UUID;
import fr.pandacube.util.EnumUtil;
public class SQLType<T> {
protected final String sqlDeclaration;
private final Class<T> javaTypes;
protected 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;
}
public static final SQLType<Boolean> BOOLEAN = new SQLType<>("BOOLEAN", Boolean.class);
public static final SQLType<Byte> TINYINT = new SQLType<>("TINYINT", Byte.class);
public static final SQLType<Byte> BYTE = TINYINT;
public static final SQLType<Short> SMALLINT = new SQLType<>("SMALLINT", Short.class);
public static final SQLType<Short> 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<>(SQLType.CHAR(36), UUID.class, UUID::fromString, UUID::toString);
}

View File

@@ -0,0 +1,23 @@
package fr.pandacube.util.orm;
import java.util.List;
import org.javatuples.Pair;
import fr.pandacube.util.Log;
public abstract class SQLWhere {
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)]";
}
}
}

View File

@@ -0,0 +1,55 @@
package fr.pandacube.util.orm;
import java.util.ArrayList;
import java.util.List;
import org.javatuples.Pair;
public class SQLWhereChain extends SQLWhere {
private SQLBoolOp operator;
private List<SQLWhere> conditions = new ArrayList<>();
public SQLWhereChain(SQLBoolOp op) {
if (op == null) throw new IllegalArgumentException("op can't be null");
operator = op;
}
public SQLWhereChain add(SQLWhere sqlWhere) {
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
conditions.add(sqlWhere);
return this;
}
@Override
public Pair<String, List<Object>> toSQL() throws ORMException {
String sql = "";
List<Object> params = new ArrayList<>();
boolean first = true;
for (SQLWhere 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);
}
public enum SQLBoolOp {
/** Equivalent to SQL "<code>AND</code>" */
AND("AND"),
/** Equivalent to SQL "<code>OR</code>" */
OR("OR");
public 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;
public class SQLWhereComp extends SQLWhere {
private SQLField<?, ?> 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
*/
public <T> SQLWhereComp(SQLField<?, 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);
}
public 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("!=");
public final String sql;
private SQLComparator(String s) {
sql = s;
}
}
}

View File

@@ -0,0 +1,33 @@
package fr.pandacube.util.orm;
import java.util.ArrayList;
import java.util.List;
import org.javatuples.Pair;
public class SQLWhereLike extends SQLWhere {
private SQLField<?, String> 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.
*/
public SQLWhereLike(SQLField<?, String> 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;
public class SQLWhereNull extends SQLWhere {
private SQLField<?, ?> 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"
*/
public SQLWhereNull(SQLField<?, ?> 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,316 @@
package fr.pandacube.util.text_display;
import java.util.Arrays;
import java.util.List;
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.TextComponent;
public class Display {
private BaseComponent root = new TextComponent("");
private BaseComponent current = null;
/*
* ****************
* * Constructors *
* ****************
*/
/**
* Create a new instance. The current component is not initialized.
*/
public Display() {}
/**
* Create a new instance, with the current component already initialized with the parameter.
* @param legacyText a text that will be converted to a component and set to the current compoment.
*/
public Display(String legacyText) {
next(legacyText);
}
/**
* Create a new instance, with the current component already initialized with the parameter.
* @param legacyText a list of text that will be joined by a line return followed by ChatColor.RESET,
* then converted to a component and set to the current component.
*/
public Display(List<String> legacyText) {
this(String.join("\n"+ChatColor.RESET, legacyText));
}
/**
* Create a new instance, with the current component already initialized with the parameter.
* @param legacyText an array of text that will be joined by a line return followed by ChatColor.RESET,
* then converted to a component and set to the current component.
*/
public Display(String[] legacyText) {
this(Arrays.asList(legacyText));
}
/**
* Create a new instance, with the current component already initialized with the parameter.
* @param firstComponent a component corresponding to the current component.
*/
public Display(BaseComponent firstComponent) {
next(firstComponent);
}
/**
* Create a new instance, with the current component already initialized with the parameter.
* @param components an array of component that will be inside the current component.
*/
public Display(BaseComponent[] components) {
if (components == null) throw new IllegalArgumentException("le paramètre ne doit pas être null");
next(components);
}
/*
* ******************
* * next() methods *
* ******************
*/
/**
* Initialize the current component with the parameter.
* The previous component is stored in the root component.
* @param cmp a component corresponding to the new component.
* @return this
*/
public Display next(BaseComponent cmp) {
if (cmp == null) throw new IllegalArgumentException("le paramètre ne doit pas être null");
finalizeCurrentComponent();
current = cmp;
return this;
}
/**
* Initialize the current component with the parameter.
* The previous component is stored in the root component.
* @param str a text that will be converted to a component and set to the current compoment.
* @return this
*/
public Display next(String str) {
return next(TextComponent.fromLegacyText(str == null ? "" : str));
}
/**
* Initialize the current component with the parameter.
* The previous component is stored in the root component.
* @param components an array of component that will be inside the current component.
* @return this
*/
public Display next(BaseComponent[] components) {
BaseComponent bc = new TextComponent();
for (BaseComponent c : components)
bc.addExtra(c);
return next(bc);
}
/**
* Initialize the current component with the parameter.
* The previous component is stored in the root component.
* @param cmp an other instance of Display that the root component become the current component of this instance.
* @return this
*/
public Display next(Display cmp) {
if (cmp == null) throw new IllegalArgumentException("le paramètre ne doit pas être null");
return next(cmp.get());
}
/**
* Initialize the current component with the text "\n".
* The previous component is stored in the root component.
* @return this
*/
public Display nextLine() {
finalizeCurrentComponent();
current = new TextComponent("\n");
return this;
}
/*
* **************************
* * Style and behaviour of *
* *** current component ****
* **************************
*/
/**
* Set the color of the current component.
* @param color the colour. Can be null;
* @return this
*/
public Display color(ChatColor color) {
current.setColor(color);
return this;
}
/**
* Set if the current component is bold.
* @param b true if bold, false if not, null if undefined
* @return this
*/
public Display bold(Boolean b) {
current.setBold(b);
return this;
}
/**
* Set if the current component is italic.
* @param b true if italic, false if not, null if undefined
* @return this
*/
public Display italic(Boolean i) {
current.setItalic(i);
return this;
}
/**
* Set if the current component is underlined.
* @param b true if underlined, false if not, null if undefined
* @return this
*/
public Display underlined(Boolean u) {
current.setUnderlined(u);
return this;
}
/**
* Set if the current component is obfuscated.
* In Minecraft user interface, obfuscated text displays randomly generated character in place of the originals. The random text regenerate each frame.
* @param b true if obfuscated, false if not, null if undefined
* @return this
*/
public Display obfuscated(Boolean o) {
current.setObfuscated(o);
return this;
}
/**
* Set if the current component is strikethrough.
* @param b true if strikethrough, false if not, null if undefined
* @return this
*/
public Display strikethrough(Boolean s) {
current.setStrikethrough(s);
return this;
}
/**
* Set a text displayed as a tooltip when the cursor is hover the current component.
* This method is only relevant if this Display is intended to be displayed in the chat or in a book
* @param content the text as an array of component.
* @return this
*/
public Display hoverText(BaseComponent[] content) {
current.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, content));
return this;
}
/**
* Set a text displayed as a tooltip when the cursor is hover the current component.
* This method is only relevant if this Display is intended to be displayed in the chat or in a book
* @param content the text as a component
* @return this
*/
public Display hoverText(BaseComponent content) {
return hoverText(new BaseComponent[] {content});
}
/**
* Set a text displayed as a tooltip when the cursor is hover the current component.
* This method is only relevant if this Display is intended to be displayed in the chat or in a book
* @param content the text as a legacy string.
* @return this
*/
public Display hoverText(String legacyContent) {
return hoverText(TextComponent.fromLegacyText(legacyContent));
}
/**
* Set a text displayed as a tooltip when the cursor is hover the current component.
* This method is only relevant if this Display is intended to be displayed in the chat or in a book
* @param content the text as a {@link Display} instance.
* @return this
*/
public Display hoverText(Display content) {
return hoverText(content.get());
}
/**
* Allow the player to click on the current component to access to the specified URL.
* This method is only relevant if this Display is intended to be displayed in the chat
* @param url the URL
* @return this
*/
public Display clickURL(String url) {
current.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url));
return this;
}
/**
* Allow the player to click on the current component to run the specified command.
* This method is only relevant if this Display is intended to be displayed in the chat, in a book or on a sign.
* On the sign, all the commands are executed in a row when the player click on the sign.
* @param cmd the command, with the "/"
* @return this
*/
public Display clickCommand(String cmd) {
current.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, cmd));
return this;
}
/**
* Allow the player to click on the current component to fill the textfield with the specified command.
* This method is only relevant if this Display is intended to be displayed in the chat.
* @param cmd the command
* @return this
*/
public Display clickSuggest(String cmd) {
current.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, cmd));
return this;
}
/**
* Allow the player to shuft-click on the current component to insert the specified string into the textfield (at the cursor location).
* This method is only relevant if this Display is intended to be displayed in the chat.
* @param str the string
* @return this
*/
public Display clickInsertion(String str) {
current.setInsertion(str);
return this;
}
private void finalizeCurrentComponent() {
if (current != null) root.addExtra(current);
current = null;
}
/**
* Add the current compoment into the root component and return the root component.
* @return
*/
public BaseComponent get() {
finalizeCurrentComponent();
return root;
}
/**
* Add the current compoment into the root component and return all the components in an array.
* @return
*/
public BaseComponent[] getArray() {
finalizeCurrentComponent();
return root.getExtra().toArray(new BaseComponent[root.getExtra().size()]);
}
}

View File

@@ -0,0 +1,420 @@
package fr.pandacube.util.text_display;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
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 DisplayUtil {
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, "@~®")
.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 final ChatColor COLOR_TITLE = ChatColor.GOLD;
public static final ChatColor COLOR_LINK = ChatColor.GREEN;
public static final ChatColor COLOR_COMMAND = ChatColor.GRAY;
public static BaseComponent createURLLink(String text, String url, String hoverText) {
return _createURLLink(new Display(text), url, hoverText);
}
public static BaseComponent createURLLink(BaseComponent text, String url, String hoverText) {
return _createURLLink(new Display(text), url, hoverText);
}
public static BaseComponent createURLLink(BaseComponent[] text, String url, String hoverText) {
return _createURLLink(new Display(text), url, hoverText);
}
private static BaseComponent _createURLLink(Display d, String url, String hoverText) {
String dispURL = (url.length() > 50) ? (url.substring(0, 48) + "...") : url;
return d.clickURL(url)
.hoverText(ChatColor.GRAY + ((hoverText == null) ? "Cliquez pour accéder au site :" : hoverText) + "\n"
+ ChatColor.GRAY + dispURL)
.color(COLOR_LINK).get();
}
public static BaseComponent createCommandLink(String text, String commandWithSlash, String hoverText) {
return createCommandLink(text, commandWithSlash, hoverText == null ? null : TextComponent.fromLegacyText(hoverText));
}
public static BaseComponent createCommandLink(String text, String commandWithSlash, BaseComponent hoverText) {
return createCommandLink(text, commandWithSlash, hoverText == null ? null : new BaseComponent[] {hoverText});
}
public static BaseComponent createCommandLink(String text, String commandWithSlash, BaseComponent[] hoverText) {
return _createCommandLink(new Display(text), commandWithSlash, hoverText);
}
private static BaseComponent _createCommandLink(Display d, String commandWithSlash, BaseComponent[] hoverText) {
d.clickCommand(commandWithSlash).color(COLOR_COMMAND);
if (hoverText != null) d.hoverText(hoverText);
return d.get();
}
public static BaseComponent createCommandSuggest(String text, String commandWithSlash, String hoverText) {
return createCommandSuggest(text, commandWithSlash, hoverText == null ? null : TextComponent.fromLegacyText(hoverText));
}
public static BaseComponent createCommandSuggest(String text, String commandWithSlash, BaseComponent hoverText) {
return createCommandSuggest(text, commandWithSlash, hoverText == null ? null : new BaseComponent[] {hoverText});
}
public static BaseComponent createCommandSuggest(String text, String commandWithSlash, BaseComponent[] hoverText) {
return _createCommandSuggest(new Display(text), commandWithSlash, hoverText);
}
private static BaseComponent _createCommandSuggest(Display d, String commandWithSlash, BaseComponent[] hoverText) {
d.clickSuggest(commandWithSlash).color(COLOR_COMMAND);
if (hoverText != null) d.hoverText(hoverText);
return d.get();
}
// TODO refaire les 4 methodes ci-dessous
public static BaseComponent centerText(BaseComponent text, char repeatedChar, ChatColor decorationColor,
boolean console) {
int textWidth = strWidth(text.toPlainText(), console, false);
if (textWidth > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH)) return text;
String current = text.toPlainText();
int count = 0;
do {
count++;
current = repeatedChar + current + repeatedChar;
} while (strWidth(current, console, false) <= ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH));
count--;
String finalLeftOrRight = "";
for (int i = 0; i < count; i++)
finalLeftOrRight += repeatedChar;
Display d = new Display().next(finalLeftOrRight).color(decorationColor).next(text);
if (repeatedChar != ' ') d.next(finalLeftOrRight).color(decorationColor);
return d.get();
}
public static BaseComponent leftText(BaseComponent text, char repeatedChar, ChatColor decorationColor, int nbLeft,
boolean console) {
int textWidth = strWidth(text.toPlainText(), console, false);
if (textWidth > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH) || textWidth
+ nbLeft * charW(repeatedChar, console, false) > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH))
return text;
Display d = new Display();
String finalLeft = "";
if (nbLeft > 0) {
for (int i = 0; i < nbLeft; i++)
finalLeft += repeatedChar;
d.next(finalLeft).color(decorationColor);
}
d.next(text);
int count = 0;
String current = finalLeft + text.toPlainText();
do {
count++;
current += repeatedChar;
} while (strWidth(current, console, false) <= ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH));
count--;
if (repeatedChar != ' ') {
String finalRight = "";
for (int i = 0; i < count; i++)
finalRight += repeatedChar;
d.next(finalRight).color(decorationColor);
}
return d.get();
}
public static BaseComponent rightText(BaseComponent text, char repeatedChar, ChatColor decorationColor, int nbRight,
boolean console) {
int textWidth = strWidth(text.toPlainText(), console, false);
if (textWidth > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH) || textWidth
+ nbRight * charW(repeatedChar, console, false) > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH))
return text;
String tempText = text.toPlainText();
if (nbRight > 0) {
tempText += decorationColor;
for (int i = 0; i < nbRight; i++)
tempText += repeatedChar;
}
int count = 0;
String current = tempText;
do {
count++;
current = repeatedChar + current;
} while (strWidth(current, console, false) <= ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH));
count--;
String finalLeft = "";
for (int i = 0; i < count; i++)
finalLeft += repeatedChar;
Display d = new Display().next(finalLeft).color(decorationColor).next(text);
if (repeatedChar != ' ') {
String finalRight = "";
for (int i = 0; i < nbRight; i++)
finalRight += repeatedChar;
d.next(finalRight).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);
String finalLine = "";
for (int i = 0; i < count; i++)
finalLine += repeatedChar;
return new Display().next(finalLine).color(decorationColor).get();
}
public static int componentWidth(BaseComponent[] components, boolean console) {
return Arrays.stream(components).mapToInt(c -> componentWidth(c, console)).sum();
}
public static int componentWidth(BaseComponent component, boolean console) {
int count = 0;
for (BaseComponent c : component.getExtra())
count += componentWidth(c, console);
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);
}
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')
bold = false;
}
else if (c == ' ') {
if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word
lines.add(currentLine);
String lastStyle = 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 = 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 = 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 getLastColors(String legacyText) {
String result = "";
int length = legacyText.length();
// Search backwards from the end as it is faster
for (int index = length - 1; index > -1; index--) {
char section = legacyText.charAt(index);
if (section == ChatColor.COLOR_CHAR && index < length - 1) {
char c = legacyText.charAt(index + 1);
ChatColor color = getChatColorByChar(c);
if (color != null) {
result = color.toString() + result;
// Once we find a color or reset we can stop searching
char col = color.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));
}
}

View File

@@ -0,0 +1,63 @@
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_decoration = ChatColor.GOLD;
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 = color_decoration + 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 + color_decoration + 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);
}
}