Pretty big commit with lot of unrelated changes

This commit is contained in:
Marc Baloup 2020-11-02 23:23:41 +01:00
parent 34e015cb01
commit ea39a7a84a
Signed by: marcbal
GPG Key ID: BBC0FE3ABC30B893
25 changed files with 1654 additions and 743 deletions

View File

@ -1,13 +1,97 @@
package fr.pandacube; package fr.pandacube;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Locale;
import java.util.TimeZone;
import fr.pandacube.util.text_display.Chat;
import net.md_5.bungee.api.ChatColor;
public class Pandacube { public class Pandacube {
public static final Locale LOCALE = Locale.FRANCE;
public static final TimeZone TIMEZONE = TimeZone.getTimeZone("Europe/Paris");
public static final Charset NETWORK_CHARSET = Charset.forName("UTF-8"); public static final Charset NETWORK_CHARSET = Charset.forName("UTF-8");
public static final int NETWORK_TCP_BUFFER_SIZE = 1024 * 1024; public static final int NETWORK_TCP_BUFFER_SIZE = 1024 * 1024;
public static final int NETWORK_TIMEOUT = 0; // no timeout (milli-seconds) public static final int NETWORK_TIMEOUT = 0; // no timeout (milli-seconds)
//public static final ChatColor CHAT_GREEN_1_NORMAL = ChatColor.of("#5f9765"); // h=126 s=23 l=48
public static final ChatColor CHAT_GREEN_1_NORMAL = ChatColor.of("#3db849"); // h=126 s=50 l=48
public static final ChatColor CHAT_GREEN_2 = ChatColor.of("#5ec969"); // h=126 s=50 l=58
public static final ChatColor CHAT_GREEN_3 = ChatColor.of("#85d68d"); // h=126 s=50 l=68
public static final ChatColor CHAT_GREEN_4 = ChatColor.of("#abe3b0"); // h=126 s=50 l=78
public static final ChatColor CHAT_GREEN_SATMAX = ChatColor.of("#00ff19"); // h=126 s=100 l=50
public static final ChatColor CHAT_GREEN_1_SAT = ChatColor.of("#20d532"); // h=126 s=50 l=48
public static final ChatColor CHAT_GREEN_2_SAT = ChatColor.of("#45e354"); // h=126 s=50 l=58
public static final ChatColor CHAT_GREEN_3_SAT = ChatColor.of("#71ea7d"); // h=126 s=50 l=68
public static final ChatColor CHAT_GREEN_4_SAT = ChatColor.of("#9df0a6"); // h=126 s=50 l=78
public static final ChatColor CHAT_BROWN_1 = ChatColor.of("#b26d3a"); // h=26 s=51 l=46
public static final ChatColor CHAT_BROWN_2 = ChatColor.of("#cd9265"); // h=26 s=51 l=60
public static final ChatColor CHAT_BROWN_3 = ChatColor.of("#e0bb9f"); // h=26 s=51 l=75
public static final ChatColor CHAT_BROWN_1_SAT = ChatColor.of("#b35c19"); // h=26 s=75 l=40
public static final ChatColor CHAT_BROWN_2_SAT = ChatColor.of("#e28136"); // h=26 s=51 l=55
public static final ChatColor CHAT_BROWN_3_SAT = ChatColor.of("#ecab79"); // h=26 s=51 l=70
public static final ChatColor CHAT_GRAY_MID = ChatColor.of("#888888");
public static final ChatColor CHAT_BROADCAST_COLOR = ChatColor.YELLOW;
public static final ChatColor CHAT_DECORATION_COLOR = CHAT_GREEN_1_NORMAL;
public static final char CHAT_DECORATION_CHAR = '-';
public static final ChatColor CHAT_URL_COLOR = CHAT_GREEN_1_NORMAL;
public static final ChatColor CHAT_COMMAND_COLOR = CHAT_GRAY_MID;
public static final ChatColor CHAT_COMMAND_HIGHLIGHTED_COLOR = ChatColor.WHITE;
public static final ChatColor CHAT_INFO_COLOR = CHAT_GREEN_4;
public static final ChatColor CHAT_SUCCESS_COLOR = CHAT_GREEN_SATMAX;
public static final ChatColor CHAT_FAILURE_COLOR = ChatColor.of("#ff3333");
public static final ChatColor CHAT_DATA_COLOR = CHAT_GRAY_MID;
public static final ChatColor CHAT_PM_PREFIX_DECORATION = Pandacube.CHAT_BROWN_2_SAT;
public static final ChatColor CHAT_PM_SELF_MESSAGE = Pandacube.CHAT_GREEN_2;
public static final ChatColor CHAT_PM_OTHER_MESSAGE = Pandacube.CHAT_GREEN_4;
public static final Chat CHAT_MESSAGE_PREFIX() {
return Chat.text("[")
.color(CHAT_BROADCAST_COLOR)
.thenDecoration("Pandacube")
.thenText("] ");
}
/**
* Number of decoration character to put between the text and the border of
* the line for left and right aligned text.
*/
public static final int CHAT_NB_CHAR_MARGIN = 1;
static {
// initialize class to avoid NCDFE when updating the plugin
@SuppressWarnings({ "deprecation", "unused" })
Class<?>
c1 = fr.pandacube.util.network_api.server.RequestAnalyser.class,
c2 = fr.pandacube.util.network_api.server.RequestAnalyser.BadRequestException.class,
c3 = fr.pandacube.util.network_api.server.Response.class,
c4 = fr.pandacube.util.text_display.ChatUtil.class;
}
} }

View File

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

View File

@ -0,0 +1,53 @@
package fr.pandacube.util;
import java.util.AbstractList;
import java.util.List;
import java.util.function.Function;
public class MappedListView<S, T> extends AbstractList<T> {
private final List<S> backend;
private final Function<S, T> getter;
private final Function<T, S> setter;
/**
*
* @param backend the list backing this list
* @param getter the function converting the value from the backing list to the value of this list.
* It is used for every operation involving reading data from the backing list and comparing existing data.
* For instance, {@link #indexOf(Object)} iterate through the backing list, converting all the values
* with {@code getter} before comparing them with the parameter of {@link #indexOf(Object)}.
* before comparing
* @param setter used for modification of the data in the list (typically {@code add} and {@code set} methods)
*/
public MappedListView(List<S> backend, Function<S, T> getter, Function<T, S> setter) {
this.backend = backend;
this.getter = getter;
this.setter = setter;
}
@Override
public int size() { return backend.size(); }
@Override
public T get(int index) { return getter.apply(backend.get(index)); }
@Override
public T set(int index, T element) { return getter.apply(backend.set(index, setter.apply(element))); }
@Override
public void add(int index, T element) { backend.add(index, setter.apply(element)); }
@Override
public T remove(int index) { return getter.apply(backend.remove(index)); }
@Override
public void clear() { backend.clear(); }
@Override
public List<T> subList(int fromIndex, int toIndex) {
return new MappedListView<S, T>(backend.subList(fromIndex, toIndex), getter, setter);
}
@Override
protected void removeRange(int fromIndex, int toIndex) {
backend.subList(fromIndex, toIndex).clear();
}
}

View File

@ -34,7 +34,12 @@ public enum MinecraftVersion {
v1_14_4(498, "1.14.4"), v1_14_4(498, "1.14.4"),
v1_15(573, "1.15"), v1_15(573, "1.15"),
v1_15_1(575, "1.15.1"), v1_15_1(575, "1.15.1"),
v1_15_2(578, "1.15.2"); v1_15_2(578, "1.15.2"),
v1_16(735, "1.16"),
v1_16_1(736, "1.16.1"),
v1_16_2(751, "1.16.2"),
v1_16_3(753, "1.16.3"),
v1_16_4(754, "1.16.4");
// IMPORTANT: don't forget to update the versionMergeDisplay value when adding a new version; // IMPORTANT: don't forget to update the versionMergeDisplay value when adding a new version;
private static Map<EnumSet<MinecraftVersion>, List<String>> versionMergeDisplay; private static Map<EnumSet<MinecraftVersion>, List<String>> versionMergeDisplay;
@ -102,14 +107,35 @@ public enum MinecraftVersion {
ImmutableList.of("1.15", "1.15.1")); ImmutableList.of("1.15", "1.15.1"));
versionMergeDisplay.put(EnumSet.of(v1_15_1, v1_15_2), versionMergeDisplay.put(EnumSet.of(v1_15_1, v1_15_2),
ImmutableList.of("1.15.1", "1.15.2")); ImmutableList.of("1.15.1", "1.15.2"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2, v1_16_3, v1_16_4),
ImmutableList.of("1.16.x"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2, v1_16_3),
ImmutableList.of("1.16-1.16.3"));
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2, v1_16_3, v1_16_4),
ImmutableList.of("1.16.1-1.16.4"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2),
ImmutableList.of("1.16-1.16.2"));
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2, v1_16_3),
ImmutableList.of("1.16.1-1.16.3"));
versionMergeDisplay.put(EnumSet.of(v1_16_2, v1_16_3, v1_16_4),
ImmutableList.of("1.16.2-1.16.4"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1),
ImmutableList.of("1.16", "1.16.1"));
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2),
ImmutableList.of("1.16.1", "1.16.2"));
versionMergeDisplay.put(EnumSet.of(v1_16_2, v1_16_3),
ImmutableList.of("1.16.2", "1.16.3"));
versionMergeDisplay.put(EnumSet.of(v1_16_3, v1_16_4),
ImmutableList.of("1.16.3", "1.16.4"));
} }
public final int versionNumber; public final int id;
public final List<String> versionDisplay; public final List<String> versionDisplay;
private MinecraftVersion(int v, String... d) { private MinecraftVersion(int v, String... d) {
versionNumber = v; id = v;
versionDisplay = Arrays.asList(d); versionDisplay = Arrays.asList(d);
} }
@ -120,19 +146,25 @@ public enum MinecraftVersion {
public static MinecraftVersion getVersion(int v) { public static MinecraftVersion getVersion(int v) {
for (MinecraftVersion mcV : values()) for (MinecraftVersion mcV : values())
if (mcV.versionNumber == v) return mcV; if (mcV.id == v) return mcV;
return null; return null;
} }
public static String displayOptimizedListOfVersions(List<MinecraftVersion> versions) { public static String displayOptimizedListOfVersionsAnd(List<MinecraftVersion> versions) {
return StringUtil.joinGrammatically(", ", " et ", getVersionsDisplayList(versions));
}
public static String displayOptimizedListOfVersionsOr(List<MinecraftVersion> versions) {
return StringUtil.joinGrammatically(", ", " et ", getVersionsDisplayList(versions)); return StringUtil.joinGrammatically(", ", " et ", getVersionsDisplayList(versions));
} }
public static final List<String> getVersionsDisplayList(List<MinecraftVersion> vList) { public static final List<String> getVersionsDisplayList(List<MinecraftVersion> vList) {
if (vList == null)
return new ArrayList<>();
Set<MinecraftVersion> vSet = EnumSet.copyOf(vList); Set<MinecraftVersion> vSet = EnumSet.copyOf(vList);
List<String> ret = new ArrayList<>(); List<String> ret = new ArrayList<>();

View File

@ -17,11 +17,11 @@ public class RandomUtil {
} }
public static <T> T arrayElement(T[] arr) { public static <T> T arrayElement(T[] arr) {
return arr[rand.nextInt(arr.length)]; return (arr == null || arr.length == 0) ? null : arr[rand.nextInt(arr.length)];
} }
public static <T> T listElement(List<T> arr) { public static <T> T listElement(List<T> arr) {
return arr.get(rand.nextInt(arr.size())); return (arr == null || arr.isEmpty()) ? null : arr.get(rand.nextInt(arr.size()));
} }
/** /**

View File

@ -33,8 +33,23 @@ public class StringUtil {
public static String repeat(String base, int count) { public static String repeat(String base, int count) {
return new String(new char[count]).replace("\0", base); int baseLength = base.length();
char[] baseChars = base.toCharArray();
char[] chars = new char[baseLength * count];
for (int i = 0; i < count; i++) {
System.arraycopy(baseChars, 0, chars, i * baseLength, baseLength);
}
return new String(chars);
}
public static String repeat(char base, int count) {
char[] chars = new char[count];
for (int i = 0; i < count; i++) {
chars[i] = base;
}
return new String(chars);
} }
} }

View File

@ -2,7 +2,9 @@ package fr.pandacube.util.commands;
import java.util.Arrays; import java.util.Arrays;
public class AbstractCommand { import fr.pandacube.util.text_display.ChatStatic;
public class AbstractCommand extends ChatStatic {
public final String commandName; public final String commandName;

View File

@ -6,6 +6,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -47,13 +48,20 @@ public interface SuggestionsSupplier<S> {
public static <S> SuggestionsSupplier<S> empty() { return (s, ti, t, a) -> Collections.emptyList(); } public static <S> SuggestionsSupplier<S> empty() { return (s, ti, t, a) -> Collections.emptyList(); }
public static <S> SuggestionsSupplier<S> fromCollectionsSupplier(Supplier<Collection<String>> streamSupplier) {
return (s, ti, token, a) -> collectFilteredStream(streamSupplier.get().stream(), token);
}
public static <S> SuggestionsSupplier<S> fromStreamSupplier(Supplier<Stream<String>> streamSupplier) {
return (s, ti, token, a) -> collectFilteredStream(streamSupplier.get(), token);
}
public static <S> SuggestionsSupplier<S> fromCollection(Collection<String> suggestions) { public static <S> SuggestionsSupplier<S> fromCollection(Collection<String> suggestions) {
return (s, ti, token, a) -> collectFilteredStream(suggestions.stream(), token); return fromStreamSupplier(suggestions::stream);
} }
public static <S> SuggestionsSupplier<S> fromArray(String... suggestions) { public static <S> SuggestionsSupplier<S> fromArray(String... suggestions) {
return (s, ti, token, a) -> collectFilteredStream(Arrays.stream(suggestions), token); return fromStreamSupplier(() -> Arrays.stream(suggestions));
} }
@ -80,6 +88,15 @@ public interface SuggestionsSupplier<S> {
}; };
} }
public static <S> SuggestionsSupplier<S> booleanValues() {
return fromCollection(Arrays.asList("true", "false"));
}
/** /**
* Create a {@link SuggestionsSupplier} that suggest numbers according to the provided range. * Create a {@link SuggestionsSupplier} that suggest numbers according to the provided range.
@ -167,9 +184,11 @@ public interface SuggestionsSupplier<S> {
}; };
} }
/** /**
* Create a {@link SuggestionsSupplier} that support greedy strings argument using the suggestion from this {@link SuggestionsSupplier}. * Create a {@link SuggestionsSupplier} that support greedy strings argument using the suggestion from this {@link SuggestionsSupplier}.
* @param args all the arguments currently in the buffer
* @param index the index of the first argument of the greedy string argument * @param index the index of the first argument of the greedy string argument
* @return * @return
*/ */
@ -206,11 +225,121 @@ public interface SuggestionsSupplier<S> {
public default SuggestionsSupplier<S> quotableString() {
return (s, ti, token, a) -> {
boolean startWithQuote = token.length() > 0 && (token.charAt(0) == '"' || token.charAt(0) == '\'');
String realToken = startWithQuote ? unescapeBrigadierQuotable(token.substring(1), token.charAt(0)) : token;
String[] argsCopy = Arrays.copyOf(a, a.length);
argsCopy[a.length - 1] = realToken;
List<String> rawResults = getSuggestions(s, ti, realToken, argsCopy);
boolean needsQuotes = false;
for (String res : rawResults) {
if (!isAllowedInBrigadierUnquotedString(res)) {
needsQuotes = true;
break;
}
}
return needsQuotes
? rawResults.stream().map(SuggestionsSupplier::escapeBrigadierQuotable).collect(Collectors.toList())
: rawResults;
};
}
// inspired from com.mojang.brigadier.StringReader#readQuotedString()
static String unescapeBrigadierQuotable(String input, char quote) {
StringBuilder builder = new StringBuilder(input.length());
boolean escaped = false;
for (char c : input.toCharArray()) {
if (escaped) {
if (c == quote || c == '\\') {
escaped = false;
} else {
builder.append('\\');
}
builder.append(c);
} else if (c == '\\') {
escaped = true;
} else if (c == quote) {
return builder.toString();
} else {
builder.append(c);
}
}
return builder.toString();
}
// from com.mojang.brigadier.StringReader#isAllowedInUnquotedString(char)
static boolean isAllowedInBrigadierUnquotedString(char c) {
return c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'
|| c == '_' || c == '-' || c == '.' || c == '+';
}
static boolean isAllowedInBrigadierUnquotedString(String s) {
for (char c : s.toCharArray())
if (!isAllowedInBrigadierUnquotedString(c))
return false;
return true;
}
static String escapeBrigadierQuotable(final String input) {
final StringBuilder result = new StringBuilder("\"");
for (int i = 0; i < input.length(); i++) {
final char c = input.charAt(i);
if (c == '\\' || c == '"') {
result.append('\\');
}
result.append(c);
}
result.append("\"");
return result.toString();
}
public default SuggestionsSupplier<S> requires(Predicate<S> check) { public default SuggestionsSupplier<S> requires(Predicate<S> check) {
return (s, ti, to, a) -> { return (s, ti, to, a) -> {
return check.test(s) ? getSuggestions(s, ti, to, a) : Collections.emptyList(); return check.test(s) ? getSuggestions(s, ti, to, a) : Collections.emptyList();
}; };
} }
/**
* Returns a new {@link SuggestionsSupplier} containing all the element of this instance then the element of the provided one,
* with all duplicated values removed using {@link Stream#distinct()}.
* @param other
* @return
*/
public default SuggestionsSupplier<S> merge(SuggestionsSupplier<S> other) {
return (s, ti, to, a) -> {
List<String> l1 = getSuggestions(s, ti, to, a);
List<String> l2 = other.getSuggestions(s, ti, to, a);
return Stream.concat(l1.stream(), l2.stream())
.distinct()
.collect(Collectors.toList());
};
}
/**
* Returns a new {@link SuggestionsSupplier} containing all the suggestions of this instance,
* but if this list is still empty, returns the suggestions from the provided one.
* @param other
* @return
*/
public default SuggestionsSupplier<S> orIfEmpty(SuggestionsSupplier<S> other) {
return (s, ti, to, a) -> {
List<String> l1 = getSuggestions(s, ti, to, a);
return !l1.isEmpty() ? l1 : other.getSuggestions(s, ti, to, a);
};
}
} }

View File

@ -10,7 +10,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import fr.pandacube.util.Log; import fr.pandacube.util.Log;
import net.md_5.bungee.api.ChatColor; import fr.pandacube.util.text_display.ChatColorUtil;
/** /**
* Classe chargeant en mémoire un fichier de configuration ou un dossier donné * Classe chargeant en mémoire un fichier de configuration ou un dossier donné
* @author Marc Baloup * @author Marc Baloup
@ -125,7 +125,7 @@ public abstract class AbstractConfig {
public static String getTranslatedColorCode(String string) { public static String getTranslatedColorCode(String string) {
return ChatColor.translateAlternateColorCodes('&', string); return ChatColorUtil.translateAlternateColorCodes('&', string);
} }

View File

@ -1,54 +1,262 @@
package fr.pandacube.util.measurement; package fr.pandacube.util.measurement;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
import fr.pandacube.Pandacube;
import fr.pandacube.util.StringUtil;
import fr.pandacube.util.commands.SuggestionsSupplier;
public class TimeUtil { 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)); private static DateTimeFormatter cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Pandacube.LOCALE);
msec -= (long) (1000 * 60 * 60 * 24) * j; private static DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Pandacube.LOCALE);
h = (int) (msec / (1000 * 60 * 60)); private static DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Pandacube.LOCALE);
msec -= (long) (1000 * 60 * 60) * h; private static DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Pandacube.LOCALE);
m = (int) (msec / (1000 * 60)); private static DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Pandacube.LOCALE);
msec -= (long) (1000 * 60) * m; private static DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Pandacube.LOCALE);
s = (int) (msec / 1000);
msec -= (long) 1000 * s;
String result = ""; private static DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Pandacube.LOCALE);
if (j > 0) result = result.concat(j + "j "); private static DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Pandacube.LOCALE);
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; public static String relativeDateFr(long displayTime, boolean showSeconds, boolean compactWords) {
result = result.concat((msec / 1000D) + "s"); long currentTime = System.currentTimeMillis();
}
if (result.equals("")) result = "0"; LocalDateTime displayDateTime = toLocalDateTime(displayTime);
result = result.trim(); LocalDateTime currentDateTime = toLocalDateTime(currentTime);
if (neg)
result = "-" + result;
long timeDiff = currentTime - displayTime;
long timeDiffSec = timeDiff / 1000;
if (timeDiffSec < -1) {
// in the future
if (timeDiffSec > -60) {
if (showSeconds)
return "dans " + (-timeDiffSec) + " secondes";
else
return "dans moins dune minute";
}
if (timeDiffSec > -60*2) // dans 2 min
return "dans ̈" + (int)Math.floor((-timeDiffSec)/60) + " minute";
if (timeDiffSec > -3600) // dans 1h
return "dans " + (int)Math.floor((-timeDiffSec)/60) + " minutes";
if (timeDiffSec > -3600*2) // dans 2h
return "dans " + (int)Math.floor((-timeDiffSec)/(3600)) + " heure";
if (timeDiffSec > -3600*12) // dans 12h
return "dans " + (int)Math.floor((-timeDiffSec)/(3600)) + " heures";
return result; LocalDateTime nextMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0).plusDays(1);
if (displayDateTime.isBefore(nextMidnight)) // aujourd'hui
return "aujourdhui à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
if (displayDateTime.isBefore(nextMidnight.plusDays(1))) // demain
return "demain à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
if (displayDateTime.isBefore(nextMidnight.plusDays(5))) // dans moins d'1 semaine
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " "
+ dayOfMonthFormatter.format(displayDateTime) + " à "
+ (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
return fullDateFr(displayTime, showSeconds, true, compactWords);
}
else {
// present and past
if (timeDiffSec <= 1)
return "maintenant";
if (timeDiffSec < 60) { // ya moins d'1 min
if (showSeconds)
return "il y a " + timeDiffSec + " secondes";
else
return "il y a moins dune minute";
}
if (timeDiffSec < 60*2) // ya moins de 2 min
return "il y a " + (int)Math.floor((timeDiffSec)/60) + " minute";
if (timeDiffSec < 3600) // ya moins d'1h
return "il y a " + (int)Math.floor((timeDiffSec)/60) + " minutes";
if (timeDiffSec < 3600*2) // ya moins de 2h
return "il y a " + (int)Math.floor((timeDiffSec)/(3600)) + " heure";
if (timeDiffSec < 3600*12) // ya moins de 12h
return "il y a " + (int)Math.floor((timeDiffSec)/(3600)) + " heures";
LocalDateTime lastMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0);
if (!displayDateTime.isBefore(lastMidnight)) // aujourd'hui
return "aujourdhui à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
if (!displayDateTime.isBefore(lastMidnight.minusDays(1))) // hier
return "hier à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
if (!displayDateTime.isBefore(lastMidnight.minusDays(6))) // ya moins d'1 semaine
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " dernier à "
+ (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
return fullDateFr(displayTime, showSeconds, true, compactWords);
}
}
public static String fullDateFr(long displayTime, boolean showSeconds, boolean showWeekday, boolean compactWords) {
LocalDateTime displayDateTime = toLocalDateTime(displayTime);
return (showWeekday ? ((compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " ") : "")
+ dayOfMonthFormatter.format(displayDateTime) + " "
+ (compactWords ? cmpMonthFormatter : monthFormatter).format(displayDateTime) + " "
+ yearFormatter.format(displayDateTime) + " à "
+ (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
}
private static LocalDateTime toLocalDateTime(long msTime) {
return Instant.ofEpochMilli(msTime).atZone(Pandacube.TIMEZONE.toZoneId()).toLocalDateTime();
}
public static String durationToLongString(long msDuration, TimeUnit hUnit, TimeUnit lUnit, boolean spaces, boolean fr, boolean leadingZeros) {
if (lUnit.compareTo(hUnit) > 0) {
TimeUnit tmp = lUnit;
lUnit = hUnit;
hUnit = tmp;
}
if (lUnit.compareTo(TimeUnit.MILLISECONDS) < 0)
lUnit = TimeUnit.MILLISECONDS;
if (hUnit.compareTo(TimeUnit.MILLISECONDS) < 0)
hUnit = TimeUnit.MILLISECONDS;
AtomicLong remainingTime = new AtomicLong(msDuration);
AtomicBoolean oneDisplayed = new AtomicBoolean(false);
final TimeUnit fLUnit = lUnit, fHUnit = hUnit;
String ret = Arrays.stream(TimeUnit.values())
.sequential()
.filter(u -> u.compareTo(fLUnit) >= 0 && u.compareTo(fHUnit) <= 0)
.sorted((u1, u2) -> u2.compareTo(u1))
.filter(u -> {
if (u.convert(remainingTime.get(), TimeUnit.MILLISECONDS) == 0 && !oneDisplayed.get())
return false;
oneDisplayed.set(true);
return true;
})
.map(u -> {
long v = u.convert(remainingTime.get(), TimeUnit.MILLISECONDS);
remainingTime.addAndGet(TimeUnit.MILLISECONDS.convert(-v, u));
return toString(v, leadingZeros ? timeUnitToLeftPadLength(u) : 1) + timeUnitToSuffix(u, fr);
})
.collect(Collectors.joining(spaces ? " " : ""));
// ensure there is at least something to display (for instance : "0s")
return oneDisplayed.get() ? ret : (toString(0, leadingZeros ? timeUnitToLeftPadLength(lUnit) : 1) + timeUnitToSuffix(lUnit, fr));
}
public static String timeUnitToSuffix(TimeUnit u, boolean fr) {
switch (u) {
case DAYS:
return fr ? "j" : "d";
case HOURS:
return "h";
case MINUTES:
return "m";
case SECONDS:
return "s";
case MILLISECONDS:
return "ms";
case MICROSECONDS:
return "μs";
case NANOSECONDS:
return "ns";
default:
throw new IllegalArgumentException("Invalid TimeUnit: " + Objects.toString(u));
}
}
public static int timeUnitToLeftPadLength(TimeUnit u) {
switch (u) {
case NANOSECONDS:
case MICROSECONDS:
case MILLISECONDS:
return 3;
case SECONDS:
case MINUTES:
case HOURS:
return 2;
case DAYS:
default:
return 1;
}
}
public static String toString(long value, int leftPad) {
String valueStr = Long.toString(value);
int padding = leftPad - valueStr.length();
if (padding <= 0)
return valueStr;
return StringUtil.repeat("0", padding) + valueStr;
}
/**
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false)}
* @param msDuration the duration in ms
* @param milliseconds if the milliseconds are displayed or not
* @return
*/
public static String durationToString(long msDuration, boolean milliseconds) {
return durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false);
} }
public static String durationToString(long msec_time) { /**
return durationToString(msec_time, false); * Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, true, true, false)}
* @param msDuration
* @return
*/
public static String durationToString(long msDuration) {
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, true, true, false);
}
/**
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, false, false, false)}
* @param msDuration
* @return
*/
public static String durationToParsableString(long msDuration) {
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, false, false, false);
} }
/** /**
* @see {@link com.earth2me.essentials.utils.DateUtil#parseDateDiff(String, boolean)} * @see {@link com.earth2me.essentials.utils.DateUtil#parseDuration(String, boolean)}
*/ */
public static long parseDateDiff(String time, boolean future) throws Exception { public static long parseDuration(String time, boolean future) throws Exception {
Pattern timePattern = Pattern.compile("(?:([0-9]+)\\s*y[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*mo[a-z]*[,\\s]*)?" 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*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*h[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*m[a-z]*[,\\s]*)?"
@ -94,5 +302,52 @@ public class TimeUtil {
if (c.after(max)) return max.getTimeInMillis(); if (c.after(max)) return max.getTimeInMillis();
return c.getTimeInMillis(); return c.getTimeInMillis();
} }
public static <S> SuggestionsSupplier<S> suggestDuration() {
return (s, ti, token, args) -> {
if (token.isEmpty()) {
return emptyTokenSuggestions;
}
List<String> remainingSuffixes = new ArrayList<>(allSuffixes);
char[] tokenChars = token.toCharArray();
String accSuffix = "";
for (int i = 0; i < tokenChars.length; i++) {
char c = tokenChars[i];
if (Character.isDigit(c)) {
scanAndRemovePastSuffixes(remainingSuffixes, accSuffix);
accSuffix = "";
continue;
}
else if (Character.isLetter(c)) {
accSuffix += c;
}
else
return Collections.emptyList();
}
String prefixToken = token.substring(0, token.length() - accSuffix.length());
return SuggestionsSupplier.collectFilteredStream(remainingSuffixes.stream(), accSuffix)
.stream()
.map(str -> prefixToken + str)
.collect(Collectors.toList());
};
}
private static List<String> allSuffixes = Arrays.asList("y", "mo", "w", "d", "h", "m", "s");
private static List<String> emptyTokenSuggestions = allSuffixes.stream().map(p -> "1" + p).collect(Collectors.toList());
private static void scanAndRemovePastSuffixes(List<String> suffixes, String foundSuffix) {
for (int i = 0; i < suffixes.size(); i++) {
if (foundSuffix.startsWith(suffixes.get(i))) {
for (int j = i; j >= 0; j--) {
suffixes.remove(j);
}
return;
}
}
}
} }

View File

@ -3,9 +3,6 @@ package fr.pandacube.util.orm;
import java.util.function.Function; import java.util.function.Function;
/** /**
*
* @author Marc
*
* @param <IT> intermediate type, the type of the value transmitted to the JDBC * @param <IT> intermediate type, the type of the value transmitted to the JDBC
* @param <JT> Java type * @param <JT> Java type
*/ */
@ -15,18 +12,14 @@ public class SQLCustomType<IT, JT> extends SQLType<JT> {
public final Function<IT, JT> dbToJavaConv; public final Function<IT, JT> dbToJavaConv;
public final Function<JT, IT> javaToDbConv; public final Function<JT, IT> javaToDbConv;
protected SQLCustomType(SQLType<IT> type, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) { /* package */ SQLCustomType(SQLType<IT> type, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) {
this(type.sqlDeclaration, type.getJavaType(), javaT, dbToJava, 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) { /* package */ SQLCustomType(String sqlD, Class<IT> intermediateJavaT, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) {
super(sqlD, javaT); super(sqlD, javaT);
intermediateJavaType = intermediateJavaT; intermediateJavaType = intermediateJavaT;
dbToJavaConv = dbToJava; dbToJavaConv = dbToJava;
javaToDbConv = javaToDb; javaToDbConv = javaToDb;
} }
// TODO tester en local
} }

View File

@ -1,6 +1,7 @@
package fr.pandacube.util.orm; package fr.pandacube.util.orm;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.sql.Date;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@ -14,12 +15,14 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringBuilder;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import fr.pandacube.util.EnumUtil;
import fr.pandacube.util.Log; import fr.pandacube.util.Log;
public abstract class SQLElement<E extends SQLElement<E>> { public abstract class SQLElement<E extends SQLElement<E>> {
@ -49,7 +52,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
fields = new SQLFieldMap<>((Class<E>)getClass()); fields = new SQLFieldMap<>((Class<E>)getClass());
// le champ id commun à toutes les tables // le champ id commun à toutes les tables
SQLField<E, Integer> idF = new SQLField<>(SQLType.INT, false, true, 0); SQLField<E, Integer> idF = new SQLField<>(INT, false, true, 0);
idF.setName("id"); idF.setName("id");
fields.addField(idF); fields.addField(idF);
@ -133,8 +136,10 @@ public abstract class SQLElement<E extends SQLElement<E>> {
return Collections.unmodifiableMap(values); return Collections.unmodifiableMap(values);
} }
public <T> void set(SQLField<E, T> field, T value) { @SuppressWarnings("unchecked")
public <T> E set(SQLField<E, T> field, T value) {
set(field, value, true); set(field, value, true);
return (E) this;
} }
/* package */ <T> void set(SQLField<E, T> sqlField, T value, boolean setModified) { /* package */ <T> void set(SQLField<E, T> sqlField, T value, boolean setModified) {
@ -220,7 +225,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void save() throws ORMException { public E save() throws ORMException {
if (!isValidForSave()) if (!isValidForSave())
throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved."); throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved.");
@ -236,7 +241,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
modifiedSinceLastSave.remove("id"); modifiedSinceLastSave.remove("id");
Map<SQLField<E, ?>, Object> modifiedValues = getOnlyModifiedValues(); Map<SQLField<E, ?>, Object> modifiedValues = getOnlyModifiedValues();
if (modifiedValues.isEmpty()) return; if (modifiedValues.isEmpty()) return (E) this;
ORM.update((Class<E>)getClass(), getFieldId().eq(getId()), modifiedValues); ORM.update((Class<E>)getClass(), getFieldId().eq(getId()), modifiedValues);
} }
@ -284,6 +289,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
} catch (SQLException e) { } catch (SQLException e) {
throw new ORMException("Error while saving data", e); throw new ORMException("Error while saving data", e);
} }
return (E) this;
} }
@ -396,5 +402,100 @@ public abstract class SQLElement<E extends SQLElement<E>> {
} }
return json; return json;
} }
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul, boolean autoIncr, T deflt) {
return new SQLField<>(t, nul, autoIncr, deflt);
}
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul) {
return new SQLField<>(t, nul);
}
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul, boolean autoIncr) {
return new SQLField<>(t, nul, autoIncr);
}
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul, T deflt) {
return new SQLField<>(t, nul, deflt);
}
protected static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> foreignKeyId(boolean nul, Class<F> fkEl) {
return SQLFKField.idFK(nul, fkEl);
}
protected static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> foreignKeyId(boolean nul, Integer deflt, Class<F> fkEl) {
return SQLFKField.idFK(nul, deflt, fkEl);
}
protected static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> foreignKey(boolean nul, Class<F> fkEl, SQLField<F, T> fkF) {
return SQLFKField.customFK(nul, fkEl, fkF);
}
protected static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> foreignKey(boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) {
return SQLFKField.customFK(nul, deflt, fkEl, fkF);
}
public static final SQLType<Boolean> BOOLEAN = new SQLType<>("BOOLEAN", Boolean.class);
public static final SQLType<Integer> TINYINT = new SQLType<>("TINYINT", Integer.class); // cant be Byte due to MYSQL JDBC Connector limitations
public static final SQLType<Integer> BYTE = TINYINT;
public static final SQLType<Integer> SMALLINT = new SQLType<>("SMALLINT", Integer.class); // cant be Short due to MYSQL JDBC Connector limitations
public static final SQLType<Integer> SHORT = SMALLINT;
public static final SQLType<Integer> INT = new SQLType<>("INT", Integer.class);
public static final SQLType<Integer> INTEGER = INT;
public static final SQLType<Long> BIGINT = new SQLType<>("BIGINT", Long.class);
public static final SQLType<Long> LONG = BIGINT;
public static final SQLType<Date> DATE = new SQLType<>("DATE", Date.class);
public static final SQLType<Float> FLOAT = new SQLType<>("FLOAT", Float.class);
public static final SQLType<Double> DOUBLE = new SQLType<>("DOUBLE", Double.class);
@Deprecated
public static final SQLType<String> CHAR(int charCount) {
if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive.");
return new SQLType<>("CHAR(" + charCount + ")", String.class);
}
public static final SQLType<String> VARCHAR(int charCount) {
if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive.");
return new SQLType<>("VARCHAR(" + charCount + ")", String.class);
}
public static final SQLType<String> TEXT = new SQLType<>("TEXT", String.class);
public static final SQLType<String> STRING = TEXT;
public static final <T extends Enum<T>> SQLType<T> ENUM(Class<T> enumType) {
if (enumType == null) throw new IllegalArgumentException("enumType can't be null.");
String enumStr = "'";
boolean first = true;
for (T el : enumType.getEnumConstants()) {
if (!first) enumStr += "', '";
first = false;
enumStr += el.name();
}
enumStr += "'";
return new SQLCustomType<>("VARCHAR(" + enumStr + ")", String.class, enumType, s -> EnumUtil.searchEnum(enumType, s), Enum::name);
}
public static final SQLType<UUID> CHAR36_UUID = new SQLCustomType<>(CHAR(36), UUID.class, UUID::fromString, UUID::toString);
} }

View File

@ -1,6 +1,5 @@
package fr.pandacube.util.orm; package fr.pandacube.util.orm;
import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -106,31 +105,25 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(() -> new ArrayList<>())); return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(() -> new ArrayList<>()));
} }
/**
* @deprecated please use {@link ORM#delete(Class, SQLWhere)} instead,
* except if you really want to fetch the data before removing them from database.
*/
@Deprecated
public synchronized void removeFromDB() { public synchronized void removeFromDB() {
List<E> storedEl = getStoredEl(); List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return; if (storedEl.isEmpty()) return;
try { try {
@SuppressWarnings("unchecked")
String sqlWhere = ""; Class<E> classEl = (Class<E>)storedEl.get(0).getClass();
boolean first = true;
for (E el : storedEl) { ORM.delete(classEl,
if (!first) sqlWhere += " OR "; storedEl.get(0).getFieldId().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList()))
first = false; );
sqlWhere += "id = " + el.getId(); for (E el : storedEl)
} el.markAsNotStored();
} catch (ORMException e) {
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); Log.severe(e);
} }
@ -150,11 +143,7 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
return new SQLElementList<>(); return new SQLElementList<>();
} }
SQLWhereOr<P> where = SQLWhere.or(); return ORM.getAll(foreignKey.getForeignElementClass(), foreignKey.getPrimaryField().in(values), orderBy, null, null);
values.forEach(v -> where.or(foreignKey.getPrimaryField().eq(v)));
return ORM.getAll(foreignKey.getForeignElementClass(), where, orderBy, null, null);
} }
@ -180,11 +169,8 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
if (values.isEmpty()) { if (values.isEmpty()) {
return new SQLElementList<>(); return new SQLElementList<>();
} }
SQLWhereOr<F> where = SQLWhere.or();
values.forEach(v -> where.or(foreignKey.eq(v)));
return ORM.getAll(foreignKey.getSQLElementType(), where, orderBy, limit, offset); return ORM.getAll(foreignKey.getSQLElementType(), foreignKey.in(values), orderBy, limit, offset);
} }

View File

@ -20,11 +20,11 @@ public class SQLFKField<F extends SQLElement<F>, T, P extends SQLElement<P>> ext
construct(fkEl, fkF); construct(fkEl, fkF);
} }
public static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Class<F> fkEl) { /* package */ static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Class<F> fkEl) {
return idFK(nul, null, fkEl); 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) { /* package */ static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Integer deflt, Class<F> fkEl) {
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null"); if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
try { try {
SQLField<F, Integer> f = ORM.getSQLIdField(fkEl); SQLField<F, Integer> f = ORM.getSQLIdField(fkEl);
@ -35,11 +35,11 @@ public class SQLFKField<F extends SQLElement<F>, T, P extends SQLElement<P>> ext
} }
} }
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) { /* package */ static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> customFK(boolean nul, Class<F> fkEl, SQLField<F, T> fkF) {
return customFK(nul, null, fkEl, fkF); 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) { /* package */ static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> customFK(boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) {
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null"); if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
return new SQLFKField<>(fkF.type, nul, deflt, fkEl, fkF); return new SQLFKField<>(fkF.type, nul, deflt, fkEl, fkF);
} }

View File

@ -17,22 +17,22 @@ public class SQLField<E extends SQLElement<E>, T> {
public final boolean autoIncrement; public final boolean autoIncrement;
/* package */ final T defaultValue; /* package */ final T defaultValue;
public SQLField(SQLType<T> t, boolean nul, boolean autoIncr, T deflt) { /* package */ SQLField(SQLType<T> t, boolean nul, boolean autoIncr, T deflt) {
type = t; type = t;
canBeNull = nul; canBeNull = nul;
autoIncrement = autoIncr; autoIncrement = autoIncr;
defaultValue = deflt; defaultValue = deflt;
} }
public SQLField(SQLType<T> t, boolean nul) { /* package */ SQLField(SQLType<T> t, boolean nul) {
this(t, nul, false, null); this(t, nul, false, null);
} }
public SQLField(SQLType<T> t, boolean nul, boolean autoIncr) { /* package */ SQLField(SQLType<T> t, boolean nul, boolean autoIncr) {
this(t, nul, autoIncr, null); this(t, nul, autoIncr, null);
} }
public SQLField(SQLType<T> t, boolean nul, T deflt) { /* package */ SQLField(SQLType<T> t, boolean nul, T deflt) {
this(t, nul, false, deflt); this(t, nul, false, deflt);
} }
@ -112,10 +112,17 @@ public class SQLField<E extends SQLElement<E>, T> {
} }
private SQLWhere<E> comp(SQLComparator c, T r) { private SQLWhere<E> comp(SQLComparator c, T r) {
if (r == null)
throw new IllegalArgumentException("The value cannot be null. Use SQLField#isNull(value) or SQLField#isNotNull(value) to check for null values");
return new SQLWhereComp<>(this, c, r); return new SQLWhereComp<>(this, c, r);
} }
public SQLWhere<E> like(String like) {
return new SQLWhereLike<>(this, like);
}
public SQLWhere<E> in(Collection<T> v) { public SQLWhere<E> in(Collection<T> v) {
return new SQLWhereIn<>(this, v); return new SQLWhereIn<>(this, v);

View File

@ -1,16 +1,11 @@
package fr.pandacube.util.orm; package fr.pandacube.util.orm;
import java.sql.Date;
import java.util.UUID;
import fr.pandacube.util.EnumUtil;
public class SQLType<T> { public class SQLType<T> {
protected final String sqlDeclaration; protected final String sqlDeclaration;
private final Class<T> javaTypes; private final Class<T> javaTypes;
protected SQLType(String sqlD, Class<T> javaT) { /* package */ SQLType(String sqlD, Class<T> javaT) {
sqlDeclaration = sqlD; sqlDeclaration = sqlD;
javaTypes = javaT; javaTypes = javaT;
} }
@ -40,56 +35,5 @@ public class SQLType<T> {
return javaTypes; 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

@ -36,10 +36,8 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
return new SQLWhereOr<>(); return new SQLWhereOr<>();
} }
public static String escapeLike(String str) {
return str.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%");
public static <E extends SQLElement<E>> SQLWhere<E> like(SQLField<E, String> f, String like) {
return new SQLWhereLike<E>(f, like);
} }
} }

View File

@ -7,7 +7,7 @@ import org.javatuples.Pair;
/* package */ class SQLWhereComp<E extends SQLElement<E>> extends SQLWhere<E> { /* package */ class SQLWhereComp<E extends SQLElement<E>> extends SQLWhere<E> {
private SQLField<?, ?> left; private SQLField<E, ?> left;
private SQLComparator comp; private SQLComparator comp;
private Object right; private Object right;
@ -18,7 +18,7 @@ import org.javatuples.Pair;
* @param c 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 * @param r the value at right of the comparison operator. Can't be null
*/ */
/* package */ <T> SQLWhereComp(SQLField<?, T> l, SQLComparator c, T r) { /* package */ <T> SQLWhereComp(SQLField<E, T> l, SQLComparator c, T r) {
if (l == null || r == null || c == null) if (l == null || r == null || c == null)
throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null"); throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null");
left = l; left = l;

View File

@ -7,7 +7,7 @@ import org.javatuples.Pair;
/* package */ class SQLWhereLike<E extends SQLElement<E>> extends SQLWhere<E> { /* package */ class SQLWhereLike<E extends SQLElement<E>> extends SQLWhere<E> {
private SQLField<E, String> field; private SQLField<E, ?> field;
private String likeExpr; private String likeExpr;
/** /**
@ -16,7 +16,7 @@ import org.javatuples.Pair;
* @param f the field at left of the LIKE keyword. Can't be null * @param f the field at left of the LIKE keyword. Can't be null
* @param like the like expression. * @param like the like expression.
*/ */
/* package */ SQLWhereLike(SQLField<E, String> f, String like) { /* package */ SQLWhereLike(SQLField<E, ?> f, String like) {
if (f == null || like == null) if (f == null || like == null)
throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null"); throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null");
field = f; field = f;

View File

@ -0,0 +1,309 @@
package fr.pandacube.util.text_display;
import java.awt.Color;
import java.util.UUID;
import fr.pandacube.Pandacube;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.ItemTag;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.hover.content.Content;
import net.md_5.bungee.api.chat.hover.content.Entity;
import net.md_5.bungee.api.chat.hover.content.Item;
import net.md_5.bungee.api.chat.hover.content.Text;
public abstract class Chat extends ChatStatic {
protected BaseComponent component;
protected boolean console = false;
public Chat(BaseComponent c) {
component = c;
}
public BaseComponent get() {
return component;
}
public BaseComponent[] getAsArray() {
return new BaseComponent[] { component };
}
public String getLegacyText() {
return component.toLegacyText();
}
public Chat then(BaseComponent subComponent) {
// here are some optimizations to avoid unnecessary component nesting
if (subComponent instanceof TextComponent) {
TextComponent txtComp = (TextComponent) subComponent;
if (!txtComp.hasFormatting() && (txtComp.getText() == null || txtComp.getText().isEmpty())) {
// no need to add the provided component to the current component.
// but eventual child component must be added
if (txtComp.getExtra() != null) {
for (BaseComponent child : txtComp.getExtra())
then(child);
}
return this;
}
}
component.addExtra(subComponent);
return this;
}
public Chat then(Chat comp) { return then(comp.get()); }
public Chat then(BaseComponent[] components) {
if (components != null) {
for (BaseComponent c : components) {
then(c);
}
}
return this;
}
public Chat thenText(Object plainText) { return then(text(plainText)); }
public Chat thenInfo(Object plainText) { return then(infoText(plainText)); }
public Chat thenSuccess(Object plainText) { return then(successText(plainText)); }
public Chat thenFailure(Object plainText) { return then(failureText(plainText)); }
public Chat thenData(Object plainText) { return then(dataText(plainText)); }
public Chat thenDecoration(Object plainText) { return then(decorationText(plainText)); }
public Chat thenPlayerName(String legacyText) { return then(playerNameText(legacyText)); }
public Chat thenNewLine() { return thenText("\n"); }
public Chat thenLegacyText(Object legacyText) { return then(legacyText(legacyText)); }
public Chat thenTranslation(String key, Object... with) { return then(translation(key, with)); }
public Chat thenKeyBind(String key) { return then(keybind(key)); }
public Chat thenScore(String name, String objective, String value) { return then(score(name, objective, value)); }
public Chat thenURLLink(Chat inner, String url, Chat hover) { return then(ChatUtil.createURLLink(inner, url, hover)); }
public Chat thenURLLink(Chat inner, String url) { return thenURLLink(inner, url, null); }
public Chat thenURLLink(String url, Chat hover) { return thenURLLink(text(url), url, hover); }
public Chat thenURLLink(String url) { return thenURLLink(text(url), url); }
public Chat thenCommandLink(Chat inner, String cmdWithSlash, Chat hover) { return then(ChatUtil.createCommandLink(inner, cmdWithSlash, hover)); }
public Chat thenCommandLink(Chat inner, String cmdWithSlash) { return thenCommandLink(inner, cmdWithSlash, null); }
public Chat thenCommandLink(String cmdWithSlash, Chat hover) { return thenCommandLink(text(cmdWithSlash), cmdWithSlash, hover); }
public Chat thenCommandLink(String cmdWithSlash) { return thenCommandLink(text(cmdWithSlash), cmdWithSlash); }
public Chat thenCommandSuggest(Chat inner, String cmdWithSlash, Chat hover) { return then(ChatUtil.createCommandSuggest(inner, cmdWithSlash, hover)); }
public Chat thenCommandSuggest(Chat inner, String cmdWithSlash) { return thenCommandSuggest(inner, cmdWithSlash, null); }
public Chat thenCommandSuggest(String cmdWithSlash, Chat hover) { return thenCommandSuggest(text(cmdWithSlash), cmdWithSlash, hover); }
public Chat thenCommandSuggest(String cmdWithSlash) { return thenCommandSuggest(text(cmdWithSlash), cmdWithSlash); }
/**
* Draws a full line with the default decoration char, colored with the default decoration color.
* @return this, for method chaining
*/
public Chat thenEmptyCharLine() {
return then(ChatUtil.emptyLine(Pandacube.CHAT_DECORATION_CHAR, Pandacube.CHAT_DECORATION_COLOR, console));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided Chat left aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenLeftTextCharLine(Chat leftText) {
return then(ChatUtil.leftText(chat().decorationColor().thenText(" ").then(leftText).thenText(" ").get(), Pandacube.CHAT_DECORATION_CHAR,
Pandacube.CHAT_DECORATION_COLOR, Pandacube.CHAT_NB_CHAR_MARGIN, console));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided component left aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenLeftTextCharLine(BaseComponent leftText) {
return thenLeftTextCharLine(chatComponent(leftText));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided Chat right aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenRightTextCharLine(Chat rightText) {
return then(ChatUtil.rightText(chat().decorationColor().thenText(" ").then(rightText).thenText(" ").get(), Pandacube.CHAT_DECORATION_CHAR,
Pandacube.CHAT_DECORATION_COLOR, Pandacube.CHAT_NB_CHAR_MARGIN, console));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided component right aligned on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenRightTextCharLine(BaseComponent leftText) {
return thenRightTextCharLine(chatComponent(leftText));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided Chat centered on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenCenterTextCharLine(Chat centerText) {
return then(ChatUtil.centerText(chat().decorationColor().thenText(" ").then(centerText).thenText(" ").get(), Pandacube.CHAT_DECORATION_CHAR,
Pandacube.CHAT_DECORATION_COLOR, console));
}
/**
* Draws a full line with the default decoration char, colored with the default decoration color,
* and with the provided component centered on the line, default to the decoration color, and surrounded with 1 space on each side.
* @return this, for method chaining
*/
public Chat thenCenterTextCharLine(BaseComponent leftText) {
return thenCenterTextCharLine(chatComponent(leftText));
}
public static class FormatableChat extends Chat {
public FormatableChat(BaseComponent c) {
super(c);
}
public FormatableChat console(boolean c) { console = c; return this; }
public FormatableChat color(ChatColor c) { component.setColor(c); return this; }
public FormatableChat color(Color c) { return color(ChatColor.of(c)); }
public FormatableChat color(String c) { return color(ChatColor.of(c)); }
public FormatableChat black() { return color(ChatColor.BLACK); }
public FormatableChat darkBlue() { return color(ChatColor.DARK_BLUE); }
public FormatableChat darkGreen() { return color(ChatColor.DARK_GREEN); }
public FormatableChat darkAqua() { return color(ChatColor.DARK_AQUA); }
public FormatableChat darkRed() { return color(ChatColor.DARK_RED); }
public FormatableChat darkPurple() { return color(ChatColor.DARK_PURPLE); }
public FormatableChat gold() { return color(ChatColor.GOLD); }
public FormatableChat gray() { return color(ChatColor.GRAY); }
public FormatableChat darkGray() { return color(ChatColor.DARK_GRAY); }
public FormatableChat blue() { return color(ChatColor.BLUE); }
public FormatableChat green() { return color(ChatColor.GREEN); }
public FormatableChat aqua() { return color(ChatColor.AQUA); }
public FormatableChat red() { return color(ChatColor.RED); }
public FormatableChat lightPurple() { return color(ChatColor.LIGHT_PURPLE); }
public FormatableChat yellow() { return color(ChatColor.YELLOW); }
public FormatableChat white() { return color(ChatColor.WHITE); }
public FormatableChat successColor() { return color(Pandacube.CHAT_SUCCESS_COLOR); }
public FormatableChat failureColor() { return color(Pandacube.CHAT_FAILURE_COLOR); }
public FormatableChat infoColor() { return color(Pandacube.CHAT_INFO_COLOR); }
public FormatableChat dataColor() { return color(Pandacube.CHAT_DATA_COLOR); }
public FormatableChat decorationColor() { return color(Pandacube.CHAT_DECORATION_COLOR); }
public FormatableChat font(String f) { component.setFont(f); return this; }
public FormatableChat bold(Boolean b) { component.setBold(b); return this; }
public FormatableChat bold() { return bold(true); }
public FormatableChat italic(Boolean i) { component.setItalic(i); return this; }
public FormatableChat italic() { return italic(true); }
public FormatableChat underlined(Boolean u) { component.setUnderlined(u); return this; }
public FormatableChat underlined() { return underlined(true); }
public FormatableChat strikethrough(Boolean s) { component.setStrikethrough(s); return this; }
public FormatableChat strikethrough() { return strikethrough(true); }
public FormatableChat obfuscated(Boolean o) { component.setObfuscated(o); return this; }
public FormatableChat obfuscated() { return obfuscated(true); }
public FormatableChat shiftClickInsertion(String i) { component.setInsertion(i); return this; }
private FormatableChat clickEvent(ClickEvent e) { component.setClickEvent(e); return this; }
private FormatableChat clickEvent(ClickEvent.Action a, String v) { return clickEvent(new ClickEvent(a, v)); }
public FormatableChat clickCommand(String cmdWithSlash) { return clickEvent(ClickEvent.Action.RUN_COMMAND, cmdWithSlash); }
public FormatableChat clickSuggest(String cmdWithSlash) { return clickEvent(ClickEvent.Action.SUGGEST_COMMAND, cmdWithSlash); }
public FormatableChat clickClipboard(String value) { return clickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, value); }
public FormatableChat clickURL(String url) { return clickEvent(ClickEvent.Action.OPEN_URL, url); }
public FormatableChat clickBookPage(int page) { return clickEvent(ClickEvent.Action.CHANGE_PAGE, Integer.toString(page)); }
private FormatableChat hoverEvent(HoverEvent e) { component.setHoverEvent(e); return this; }
private FormatableChat hoverEvent(HoverEvent.Action a, Content v) { return hoverEvent(new HoverEvent(a, v)); }
private FormatableChat hoverText(Text v) { return hoverEvent(HoverEvent.Action.SHOW_TEXT, v); }
@SuppressWarnings("deprecation")
public FormatableChat hoverText(BaseComponent v) {
try {
return hoverText(new Text( new BaseComponent[] {v}));
} catch (NoSuchMethodError e) {
return hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new BaseComponent[] {v}));
}
}
public FormatableChat hoverText(Chat v) { return hoverText(v.get()); }
public FormatableChat hoverText(String legacyText) { return hoverText(legacyText(legacyText)); }
private FormatableChat hoverItem(Item v) { return hoverEvent(HoverEvent.Action.SHOW_ITEM, v); }
/** @param id namespaced item id */
public FormatableChat hoverItem(String id, int stackSize, ItemTag tag) { return hoverItem(new Item(id, stackSize, tag)); }
/** @param id namespaced item id */
public FormatableChat hoverItem(String id, int stackSize) { return hoverItem(id, stackSize, null); }
/** @param id namespaced item id */
public FormatableChat hoverItem(String id, ItemTag tag) { return hoverItem(id, -1, tag); }
/** @param id namespaced item id */
public FormatableChat hoverItem(String id) { return hoverItem(id, -1, null); }
public FormatableChat hoverEntity(Entity e) { return hoverEvent(HoverEvent.Action.SHOW_ENTITY, e); }
/** @param type namespaced entity type
* @param id cannot be null */
public FormatableChat hoverEntity(String type, UUID id, BaseComponent displayName) { return hoverEntity(new Entity(type, id.toString(), displayName)); }
/** @param type namespaced entity type
* @param id cannot be null */
public FormatableChat hoverEntity(String type, UUID id) { return hoverEntity(type, id, null); }
}
/* package */ static Object[] filterChatToBaseComponent(Object[] values) {
if (values == null)
return null;
for (int i = 0; i < values.length; i++) {
Object v = values[i];
if (v instanceof Chat)
values[i] = ((Chat) v).get();
}
return values;
}
}

View File

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

View File

@ -0,0 +1,73 @@
package fr.pandacube.util.text_display;
import java.util.Objects;
import fr.pandacube.util.text_display.Chat.FormatableChat;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.KeybindComponent;
import net.md_5.bungee.api.chat.Keybinds;
import net.md_5.bungee.api.chat.ScoreComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
public abstract class ChatStatic {
public static FormatableChat chatComponent(BaseComponent c) {
return new FormatableChat(c);
}
public static FormatableChat chat() {
return chatComponent(new TextComponent());
}
public static FormatableChat chatComponent(BaseComponent[] c) {
return chatComponent(new TextComponent(c));
}
public static FormatableChat text(Object plainText) {
return chatComponent(new TextComponent(Objects.toString(plainText)));
}
public static FormatableChat legacyText(Object legacyText) {
return chatComponent(TextComponent.fromLegacyText(Objects.toString(legacyText), null));
}
public static FormatableChat infoText(Object plainText) {
return text(plainText).infoColor();
}
public static FormatableChat dataText(Object plainText) {
return text(plainText).dataColor();
}
public static FormatableChat decorationText(Object plainText) {
return text(plainText).decorationColor();
}
public static FormatableChat successText(Object plainText) {
return text(plainText).successColor();
}
public static FormatableChat failureText(Object plainText) {
return text(plainText).failureColor();
}
public static FormatableChat playerNameText(String legacyText) {
return legacyText(legacyText).white();
}
public static FormatableChat translation(String key, Object... with) {
return chatComponent(new TranslatableComponent(key, Chat.filterChatToBaseComponent(with)));
}
/** @param key one of the values in {@link Keybinds}. */
public static FormatableChat keybind(String key) {
return chatComponent(new KeybindComponent(key));
}
public static FormatableChat score(String name, String objective, String value) {
return chatComponent(new ScoreComponent(name, objective, value));
}
}

View File

@ -1,5 +1,10 @@
package fr.pandacube.util.text_display; package fr.pandacube.util.text_display;
import static fr.pandacube.util.text_display.Chat.chatComponent;
import static fr.pandacube.util.text_display.ChatStatic.chat;
import static fr.pandacube.util.text_display.ChatStatic.legacyText;
import static fr.pandacube.util.text_display.ChatStatic.text;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -9,12 +14,14 @@ import java.util.TreeSet;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import fr.pandacube.Pandacube;
import fr.pandacube.util.text_display.Chat.FormatableChat;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent; import net.md_5.bungee.api.chat.TranslatableComponent;
public class DisplayUtil { public class ChatUtil {
public static final int DEFAULT_CHAR_SIZE = 6; public static final int DEFAULT_CHAR_SIZE = 6;
public static final Map<Integer, String> CHARS_SIZE = new ImmutableMap.Builder<Integer, String>() public static final Map<Integer, String> CHARS_SIZE = new ImmutableMap.Builder<Integer, String>()
@ -34,33 +41,31 @@ public class DisplayUtil {
public static final int BOOK_WIDTH = 116; public static final int BOOK_WIDTH = 116;
public static final int CONSOLE_NB_CHAR_DEFAULT = 50; public static final int CONSOLE_NB_CHAR_DEFAULT = 50;
public static final ChatColor COLOR_TITLE = ChatColor.GOLD; public static BaseComponent createURLLink(String text, String url) {
public static final ChatColor COLOR_LINK = ChatColor.GREEN; return createURLLink(legacyText(text), url, null);
public static final ChatColor COLOR_COMMAND = ChatColor.GRAY; }
public static BaseComponent createURLLink(String text, String url, String hoverText) { public static BaseComponent createURLLink(String text, String url, String hoverText) {
return _createURLLink(new Display(text), url, hoverText); return createURLLink(legacyText(text), url, hoverText != null ? legacyText(hoverText) : null);
}
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) { /* package */ static BaseComponent createURLLink(Chat element, String url, Chat hover) {
String dispURL = (url.length() > 50) ? (url.substring(0, 48) + "...") : url; String dispURL = (url.length() > 50) ? (url.substring(0, 48) + "...") : url;
return d.clickURL(url) return chat()
.hoverText(ChatColor.GRAY + ((hoverText == null) ? "Cliquez pour accéder au site :" : hoverText) + "\n" .clickURL(url)
+ ChatColor.GRAY + dispURL) .color(Pandacube.CHAT_URL_COLOR)
.color(COLOR_LINK).get(); .hoverText(
hover != null ? hover : Chat.text(dispURL)
)
.then(element)
.get();
} }
@ -71,19 +76,19 @@ public class DisplayUtil {
public static BaseComponent createCommandLink(String text, String commandWithSlash, String hoverText) { public static BaseComponent createCommandLink(String text, String commandWithSlash, String hoverText) {
return createCommandLink(text, commandWithSlash, hoverText == null ? null : TextComponent.fromLegacyText(hoverText)); return createCommandLink(text, commandWithSlash, hoverText == null ? null : legacyText(hoverText));
} }
public static BaseComponent createCommandLink(String text, String commandWithSlash, BaseComponent hoverText) { public static BaseComponent createCommandLink(String text, String commandWithSlash, Chat hoverText) {
return createCommandLink(text, commandWithSlash, hoverText == null ? null : new BaseComponent[] {hoverText}); return createCommandLink(legacyText(text), commandWithSlash, 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) { /* package */ static BaseComponent createCommandLink(Chat d, String commandWithSlash, Chat hoverText) {
d.clickCommand(commandWithSlash).color(COLOR_COMMAND); FormatableChat c = chat()
if (hoverText != null) d.hoverText(hoverText); .clickCommand(commandWithSlash)
return d.get(); .color(Pandacube.CHAT_COMMAND_COLOR);
if (hoverText != null)
c.hoverText(hoverText);
return c.then(d).get();
} }
@ -96,19 +101,19 @@ public class DisplayUtil {
public static BaseComponent createCommandSuggest(String text, String commandWithSlash, String hoverText) { public static BaseComponent createCommandSuggest(String text, String commandWithSlash, String hoverText) {
return createCommandSuggest(text, commandWithSlash, hoverText == null ? null : TextComponent.fromLegacyText(hoverText)); return createCommandSuggest(text, commandWithSlash, hoverText == null ? null : legacyText(hoverText));
} }
public static BaseComponent createCommandSuggest(String text, String commandWithSlash, BaseComponent hoverText) { public static BaseComponent createCommandSuggest(String text, String commandWithSlash, Chat hoverText) {
return createCommandSuggest(text, commandWithSlash, hoverText == null ? null : new BaseComponent[] {hoverText}); return createCommandSuggest(legacyText(text), commandWithSlash, 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) { /* package */ static BaseComponent createCommandSuggest(Chat d, String commandWithSlash, Chat hoverText) {
d.clickSuggest(commandWithSlash).color(COLOR_COMMAND); FormatableChat c = chat()
if (hoverText != null) d.hoverText(hoverText); .clickSuggest(commandWithSlash)
return d.get(); .color(Pandacube.CHAT_COMMAND_COLOR);
if (hoverText != null)
c.hoverText(hoverText);
return c.then(d).get();
} }
@ -134,32 +139,33 @@ public class DisplayUtil {
pagesToDisplay.add(i); pagesToDisplay.add(i);
} }
Display d = new Display(prefix); Chat d = chat().thenLegacyText(prefix);
boolean first = true; boolean first = true;
int previous = 0; int previous = 0;
for (int page : pagesToDisplay) { for (int page : pagesToDisplay) {
if (!first) { if (!first) {
if (page == previous + 1) { if (page == previous + 1) {
d.next(" "); d.thenText(" ");
} }
else { else {
if (cmdFormat.endsWith("%d")) { if (cmdFormat.endsWith("%d")) {
d.next(" "); d.thenText(" ");
d.next(createCommandSuggest("...", cmdFormat.substring(0, cmdFormat.length() - 2), "Choisir la page")); d.then(createCommandSuggest("...", cmdFormat.substring(0, cmdFormat.length() - 2), "Choisir la page"));
d.next(" "); d.thenText(" ");
} }
else else
d.next(" ... "); d.thenText(" ... ");
} }
} }
else else
first = false; first = false;
d.next(createCommandLink(Integer.toString(page), String.format(cmdFormat, page), "Aller à la page " + page)); FormatableChat pDisp = chatComponent(createCommandLink(Integer.toString(page), String.format(cmdFormat, page), "Aller à la page " + page));
if (page == currentPage) { if (page == currentPage) {
d.color(ChatColor.WHITE); pDisp.color(Pandacube.CHAT_COMMAND_HIGHLIGHTED_COLOR);
} }
d.then(pDisp);
previous = page; previous = page;
} }
@ -175,33 +181,31 @@ public class DisplayUtil {
// TODO refaire les 4 methodes ci-dessous
public static BaseComponent centerText(BaseComponent text, char repeatedChar, ChatColor decorationColor, public static BaseComponent centerText(BaseComponent text, char repeatedChar, ChatColor decorationColor,
boolean console) { boolean console) {
int textWidth = strWidth(text.toPlainText(), console, false); int textWidth = componentWidth(text, console);
if (textWidth > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH)) return text; int maxWidth = (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH;
if (textWidth > maxWidth)
return text;
int repeatedCharWidth = charW(repeatedChar, console, false);
int sideWidth = (maxWidth - textWidth) / 2;
int sideNbChar = sideWidth / repeatedCharWidth;
if (sideNbChar == 0)
return text;
String sideChars = repeatedChar(repeatedChar, sideNbChar);
String current = text.toPlainText(); Chat d = Chat.chat()
int count = 0; .then(text(sideChars).color(decorationColor))
do { .then(text);
count++; if (repeatedChar != ' ')
current = repeatedChar + current + repeatedChar; d.then(text(sideChars).color(decorationColor));
} 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(); return d.get();
@ -209,91 +213,60 @@ public class DisplayUtil {
public static BaseComponent leftText(BaseComponent text, char repeatedChar, ChatColor decorationColor, int nbLeft, public static BaseComponent leftText(BaseComponent text, char repeatedChar, ChatColor decorationColor, int nbLeft,
boolean console) { boolean console) {
int textWidth = strWidth(text.toPlainText(), console, false); int textWidth = componentWidth(text, console);
if (textWidth > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH) || textWidth int maxWidth = (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH;
+ nbLeft * charW(repeatedChar, console, false) > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH)) int repeatedCharWidth = charW(repeatedChar, console, false);
int leftWidth = nbLeft * repeatedCharWidth;
if (textWidth + leftWidth > maxWidth)
return text; return text;
Display d = new Display(); int rightNbChar = (maxWidth - (textWidth + leftWidth)) / repeatedCharWidth;
String finalLeft = ""; Chat d = chat()
if (nbLeft > 0) { .then(text(repeatedChar(repeatedChar, nbLeft)).color(decorationColor))
for (int i = 0; i < nbLeft; i++) .then(text);
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 != ' ') { if (repeatedChar != ' ') {
String finalRight = ""; d.then(text(repeatedChar(repeatedChar, rightNbChar)).color(decorationColor));
for (int i = 0; i < count; i++)
finalRight += repeatedChar;
d.next(finalRight).color(decorationColor);
} }
return d.get(); return d.get();
} }
public static BaseComponent rightText(BaseComponent text, char repeatedChar, ChatColor decorationColor, int nbRight, public static BaseComponent rightText(BaseComponent text, char repeatedChar, ChatColor decorationColor, int nbRight,
boolean console) { boolean console) {
int textWidth = strWidth(text.toPlainText(), console, false); int textWidth = componentWidth(text, console);
if (textWidth > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH) || textWidth int maxWidth = (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH;
+ nbRight * charW(repeatedChar, console, false) > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH)) int repeatedCharWidth = charW(repeatedChar, console, false);
int rightWidth = nbRight * repeatedCharWidth;
if (textWidth + rightWidth > maxWidth)
return text; return text;
String tempText = text.toPlainText(); int leftNbChar = (maxWidth - (textWidth + rightWidth)) / repeatedCharWidth;
if (nbRight > 0) {
tempText += decorationColor; Chat d = chat()
for (int i = 0; i < nbRight; i++) .then(text(repeatedChar(repeatedChar, leftNbChar)).color(decorationColor))
tempText += repeatedChar; .then(text);
}
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 != ' ') { if (repeatedChar != ' ') {
String finalRight = ""; d.then(text(repeatedChar(repeatedChar, nbRight)).color(decorationColor));
for (int i = 0; i < nbRight; i++)
finalRight += repeatedChar;
d.next(finalRight).color(decorationColor);
} }
return d.get(); return d.get();
} }
public static BaseComponent emptyLine(char repeatedChar, ChatColor decorationColor, boolean console) { public static BaseComponent emptyLine(char repeatedChar, ChatColor decorationColor, boolean console) {
int count = ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH) / charW(repeatedChar, console, false); int count = ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH) / charW(repeatedChar, console, false);
String finalLine = ""; return text(repeatedChar(repeatedChar, count)).color(decorationColor).get();
for (int i = 0; i < count; i++)
finalLine += repeatedChar;
return new Display().next(finalLine).color(decorationColor).get();
} }
private static String repeatedChar(char repeatedChar, int count) {
char[] c = new char[count];
Arrays.fill(c, repeatedChar);
return new String(c);
}
@ -304,13 +277,23 @@ public class DisplayUtil {
public static int componentWidth(BaseComponent[] components, boolean console) { public static int componentWidth(BaseComponent[] components, boolean console) {
return Arrays.stream(components).mapToInt(c -> componentWidth(c, console)).sum(); if (components == null)
return 0;
int count = 0;
for (BaseComponent c : components)
count += componentWidth(c, console);
return count;
} }
public static int componentWidth(BaseComponent component, boolean console) { public static int componentWidth(BaseComponent component, boolean console) {
if (component == null)
return 0;
int count = 0; int count = 0;
for (BaseComponent c : component.getExtra())
count += componentWidth(c, console);
if (component instanceof TextComponent) { if (component instanceof TextComponent) {
count += strWidth(((TextComponent)component).getText(), console, component.isBold()); count += strWidth(((TextComponent)component).getText(), console, component.isBold());
} }
@ -318,6 +301,11 @@ public class DisplayUtil {
for (BaseComponent c : ((TranslatableComponent)component).getWith()) for (BaseComponent c : ((TranslatableComponent)component).getWith())
count += componentWidth(c, console); count += componentWidth(c, console);
} }
if (component.getExtra() != null) {
for (BaseComponent c : component.getExtra())
count += componentWidth(c, console);
}
return count; return count;
} }
@ -368,14 +356,15 @@ public class DisplayUtil {
if ((c >= '0' && c <= '9') // reset bold if ((c >= '0' && c <= '9') // reset bold
|| (c >= 'a' && c <= 'f') || (c >= 'a' && c <= 'f')
|| (c >= 'A' && c <= 'F') || (c >= 'A' && c <= 'F')
|| c == 'r' || c == 'R') || c == 'r' || c == 'R'
|| c == 'x' || c == 'X')
bold = false; bold = false;
} }
else if (c == ' ') { else if (c == ' ') {
if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word
lines.add(currentLine); lines.add(currentLine);
String lastStyle = getLastColors(currentLine); String lastStyle = ChatColorUtil.getLastColors(currentLine);
if (currentWord.charAt(0) == ' ') { if (currentWord.charAt(0) == ' ') {
currentWord = currentWord.substring(1); currentWord = currentWord.substring(1);
currentWordSize -= charW(' ', false, firstCharCurrentWorldBold); currentWordSize -= charW(' ', false, firstCharCurrentWorldBold);
@ -394,7 +383,7 @@ public class DisplayUtil {
else if (c == '\n') { else if (c == '\n') {
if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word
lines.add(currentLine); lines.add(currentLine);
String lastStyle = getLastColors(currentLine); String lastStyle = ChatColorUtil.getLastColors(currentLine);
if (currentWord.charAt(0) == ' ') { if (currentWord.charAt(0) == ' ') {
currentWord = currentWord.substring(1); currentWord = currentWord.substring(1);
currentWordSize -= charW(' ', false, firstCharCurrentWorldBold); currentWordSize -= charW(' ', false, firstCharCurrentWorldBold);
@ -408,7 +397,7 @@ public class DisplayUtil {
} }
// wrap after // wrap after
lines.add(currentLine); lines.add(currentLine);
String lastStyle = getLastColors(currentLine); String lastStyle = ChatColorUtil.getLastColors(currentLine);
currentLine = lastStyle.equals("§r") ? "" : lastStyle; currentLine = lastStyle.equals("§r") ? "" : lastStyle;
currentLineSize = 0; currentLineSize = 0;
@ -438,123 +427,46 @@ public class DisplayUtil {
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));
}
public static String truncatePrefix(String prefix, int maxLength) {
if (prefix.length() > maxLength) {
String lastColor = ChatColorUtil.getLastColors(prefix);
prefix = truncateAtLengthWithoutReset(prefix, maxLength);
if (!ChatColorUtil.getLastColors(prefix).equals(lastColor))
prefix = truncateAtLengthWithoutReset(prefix, maxLength - lastColor.length()) + lastColor;
}
return prefix;
}
public static String truncateAtLengthWithoutReset(String prefix, int l) {
if (prefix.length() > l) {
prefix = prefix.substring(0, l);
if (prefix.endsWith("§"))
prefix = prefix.substring(0, prefix.length()-1);
}
return prefix;
}
/**
* Force a text to be italic, while keeping other formatting and colors. public static BaseComponent toUniqueBaseComponent(BaseComponent... baseComponents) {
* The text is prefixed with the ITALIC tag, but is not reset at the end. if (baseComponents == null || baseComponents.length == 0)
* @param legacyText the original text return new TextComponent();
* @return the text fully italic if (baseComponents.length == 1)
*/ return baseComponents[0];
public static String forceItalic(String legacyText) { return new TextComponent(baseComponents);
return forceFormat(legacyText, ChatColor.ITALIC);
} }
/**
* Force a text to be bold, while keeping other formatting and colors.
* The text is prefixed with the BOLD tag, but is not reset at the end.
* @param legacyText the original text
* @return the text fully bold
*/
public static String forceBold(String legacyText) {
return forceFormat(legacyText, ChatColor.BOLD);
}
/**
* Force a text to be underlined, while keeping other formatting and colors.
* The text is prefixed with the UNDERLINE tag, but is not reset at the end.
* @param legacyText the original text
* @return the text fully underlined
*/
public static String forceUnderline(String legacyText) {
return forceFormat(legacyText, ChatColor.UNDERLINE);
}
/**
* Force a text to be stroked through, while keeping other formatting and colors.
* The text is prefixed with the STRIKETHROUGH tag, but is not reset at the end.
* @param legacyText the original text
* @return the text fully stroked through
*/
public static String forceStrikethrough(String legacyText) {
return forceFormat(legacyText, ChatColor.STRIKETHROUGH);
}
/**
* Force a text to be obfuscated, while keeping other formatting and colors.
* The text is prefixed with the MAGIC tag, but is not reset at the end.
* @param legacyText the original text
* @return the text fully obfuscated
*/
public static String forceObfuscated(String legacyText) {
return forceFormat(legacyText, ChatColor.MAGIC);
}
private static String forceFormat(String legacyText, ChatColor format) {
return format + legacyText
.replace(format.toString(), "") // remove previous tag to make the result cleaner
.replaceAll("§([a-frA-FR0-9])", "§$1" + format);
}
/**
* Replace the RESET tag of the input string to the specified color tag.
* @param legacyText the original text
* @param color the color to used to replace the RESET tag
* (can be a combination of a color tag followed by multiple format tag)
* @return the resulting text
*/
public static String resetToColor(String legacyText, String color) {
return legacyText.replace(ChatColor.RESET.toString(), color);
}

View File

@ -1,331 +0,0 @@
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) {
if (components != null && components.length == 1)
return next(components[0]);
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 copy content to the clipboard.
* @param str the string to copy to clipboard
* @return this
*/
public Display clickClipboard(String str) {
current.setClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, str));
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();
if (!root.hasFormatting() && root.getExtra() != null && root.getExtra().size() == 1)
return root.getExtra().get(0);
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

@ -6,7 +6,6 @@ public class TextProgressBar {
private static String pattern_start = "["; private static String pattern_start = "[";
private static String pattern_end = "]"; private static String pattern_end = "]";
private static ChatColor color_empty = ChatColor.DARK_GRAY; private static ChatColor color_empty = ChatColor.DARK_GRAY;
private static ChatColor color_decoration = ChatColor.GOLD;
private static ChatColor color_default = ChatColor.RESET; private static ChatColor color_default = ChatColor.RESET;
private static String pattern_empty = "."; private static String pattern_empty = ".";
private static String pattern_full = "|"; private static String pattern_full = "|";
@ -35,7 +34,7 @@ public class TextProgressBar {
} }
int sum_sizes = 0; int sum_sizes = 0;
String bar = color_decoration + pattern_start; String bar = pattern_start;
for (int i = 0; i < sizes.length; i++) { for (int i = 0; i < sizes.length; i++) {
sum_sizes += sizes[i]; sum_sizes += sizes[i];
@ -52,7 +51,7 @@ public class TextProgressBar {
for (int j = 0; j < (max_size - sum_sizes); j++) for (int j = 0; j < (max_size - sum_sizes); j++)
bar = bar + pattern_empty; bar = bar + pattern_empty;
bar = bar + color_decoration + pattern_end; bar = bar + ChatColor.RESET + pattern_end;
return bar; return bar;
} }