From ea39a7a84a8d7442892873f7f28e40dc3774491e Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Mon, 2 Nov 2020 23:23:41 +0100 Subject: [PATCH] Pretty big commit with lot of unrelated changes --- src/main/java/fr/pandacube/Pandacube.java | 84 ++++ .../pandacube/util/AmountPerTimeLimiter.java | 57 +++ .../fr/pandacube/util/MappedListView.java | 53 +++ .../fr/pandacube/util/MinecraftVersion.java | 42 +- .../java/fr/pandacube/util/RandomUtil.java | 4 +- .../java/fr/pandacube/util/StringUtil.java | 19 +- .../util/commands/AbstractCommand.java | 4 +- .../util/commands/SuggestionsSupplier.java | 135 +++++- .../pandacube/util/config/AbstractConfig.java | 4 +- .../pandacube/util/measurement/TimeUtil.java | 319 ++++++++++++-- .../fr/pandacube/util/orm/SQLCustomType.java | 11 +- .../fr/pandacube/util/orm/SQLElement.java | 109 ++++- .../fr/pandacube/util/orm/SQLElementList.java | 46 +- .../fr/pandacube/util/orm/SQLFKField.java | 8 +- .../java/fr/pandacube/util/orm/SQLField.java | 15 +- .../java/fr/pandacube/util/orm/SQLType.java | 58 +-- .../java/fr/pandacube/util/orm/SQLWhere.java | 6 +- .../fr/pandacube/util/orm/SQLWhereComp.java | 4 +- .../fr/pandacube/util/orm/SQLWhereLike.java | 4 +- .../fr/pandacube/util/text_display/Chat.java | 309 ++++++++++++++ .../util/text_display/ChatColorUtil.java | 293 +++++++++++++ .../util/text_display/ChatStatic.java | 73 ++++ .../{DisplayUtil.java => ChatUtil.java} | 404 +++++++----------- .../pandacube/util/text_display/Display.java | 331 -------------- .../util/text_display/TextProgressBar.java | 5 +- 25 files changed, 1654 insertions(+), 743 deletions(-) create mode 100644 src/main/java/fr/pandacube/util/AmountPerTimeLimiter.java create mode 100644 src/main/java/fr/pandacube/util/MappedListView.java create mode 100644 src/main/java/fr/pandacube/util/text_display/Chat.java create mode 100644 src/main/java/fr/pandacube/util/text_display/ChatColorUtil.java create mode 100644 src/main/java/fr/pandacube/util/text_display/ChatStatic.java rename src/main/java/fr/pandacube/util/text_display/{DisplayUtil.java => ChatUtil.java} (50%) delete mode 100644 src/main/java/fr/pandacube/util/text_display/Display.java diff --git a/src/main/java/fr/pandacube/Pandacube.java b/src/main/java/fr/pandacube/Pandacube.java index 04da388..feb690f 100644 --- a/src/main/java/fr/pandacube/Pandacube.java +++ b/src/main/java/fr/pandacube/Pandacube.java @@ -1,13 +1,97 @@ package fr.pandacube; import java.nio.charset.Charset; +import java.util.Locale; +import java.util.TimeZone; + +import fr.pandacube.util.text_display.Chat; +import net.md_5.bungee.api.ChatColor; public class Pandacube { + + public static final Locale LOCALE = Locale.FRANCE; + + public static final TimeZone TIMEZONE = TimeZone.getTimeZone("Europe/Paris"); public static final Charset NETWORK_CHARSET = Charset.forName("UTF-8"); public static final int NETWORK_TCP_BUFFER_SIZE = 1024 * 1024; public static final int NETWORK_TIMEOUT = 0; // no timeout (milli-seconds) + + + + + + //public static final ChatColor CHAT_GREEN_1_NORMAL = ChatColor.of("#5f9765"); // h=126 s=23 l=48 + + public static final ChatColor CHAT_GREEN_1_NORMAL = ChatColor.of("#3db849"); // h=126 s=50 l=48 + public static final ChatColor CHAT_GREEN_2 = ChatColor.of("#5ec969"); // h=126 s=50 l=58 + public static final ChatColor CHAT_GREEN_3 = ChatColor.of("#85d68d"); // h=126 s=50 l=68 + public static final ChatColor CHAT_GREEN_4 = ChatColor.of("#abe3b0"); // h=126 s=50 l=78 + + public static final ChatColor CHAT_GREEN_SATMAX = ChatColor.of("#00ff19"); // h=126 s=100 l=50 + public static final ChatColor CHAT_GREEN_1_SAT = ChatColor.of("#20d532"); // h=126 s=50 l=48 + public static final ChatColor CHAT_GREEN_2_SAT = ChatColor.of("#45e354"); // h=126 s=50 l=58 + public static final ChatColor CHAT_GREEN_3_SAT = ChatColor.of("#71ea7d"); // h=126 s=50 l=68 + public static final ChatColor CHAT_GREEN_4_SAT = ChatColor.of("#9df0a6"); // h=126 s=50 l=78 + + public static final ChatColor CHAT_BROWN_1 = ChatColor.of("#b26d3a"); // h=26 s=51 l=46 + public static final ChatColor CHAT_BROWN_2 = ChatColor.of("#cd9265"); // h=26 s=51 l=60 + public static final ChatColor CHAT_BROWN_3 = ChatColor.of("#e0bb9f"); // h=26 s=51 l=75 + + public static final ChatColor CHAT_BROWN_1_SAT = ChatColor.of("#b35c19"); // h=26 s=75 l=40 + public static final ChatColor CHAT_BROWN_2_SAT = ChatColor.of("#e28136"); // h=26 s=51 l=55 + public static final ChatColor CHAT_BROWN_3_SAT = ChatColor.of("#ecab79"); // h=26 s=51 l=70 + + public static final ChatColor CHAT_GRAY_MID = ChatColor.of("#888888"); + + public static final ChatColor CHAT_BROADCAST_COLOR = ChatColor.YELLOW; + + + public static final ChatColor CHAT_DECORATION_COLOR = CHAT_GREEN_1_NORMAL; + public static final char CHAT_DECORATION_CHAR = '-'; + public static final ChatColor CHAT_URL_COLOR = CHAT_GREEN_1_NORMAL; + public static final ChatColor CHAT_COMMAND_COLOR = CHAT_GRAY_MID; + public static final ChatColor CHAT_COMMAND_HIGHLIGHTED_COLOR = ChatColor.WHITE; + public static final ChatColor CHAT_INFO_COLOR = CHAT_GREEN_4; + public static final ChatColor CHAT_SUCCESS_COLOR = CHAT_GREEN_SATMAX; + public static final ChatColor CHAT_FAILURE_COLOR = ChatColor.of("#ff3333"); + public static final ChatColor CHAT_DATA_COLOR = CHAT_GRAY_MID; + + + public static final ChatColor CHAT_PM_PREFIX_DECORATION = Pandacube.CHAT_BROWN_2_SAT; + public static final ChatColor CHAT_PM_SELF_MESSAGE = Pandacube.CHAT_GREEN_2; + public static final ChatColor CHAT_PM_OTHER_MESSAGE = Pandacube.CHAT_GREEN_4; + + + public static final Chat CHAT_MESSAGE_PREFIX() { + return Chat.text("[") + .color(CHAT_BROADCAST_COLOR) + .thenDecoration("Pandacube") + .thenText("] "); + } + + + + + /** + * Number of decoration character to put between the text and the border of + * the line for left and right aligned text. + */ + public static final int CHAT_NB_CHAR_MARGIN = 1; + + + + + static { + // initialize class to avoid NCDFE when updating the plugin + @SuppressWarnings({ "deprecation", "unused" }) + Class + c1 = fr.pandacube.util.network_api.server.RequestAnalyser.class, + c2 = fr.pandacube.util.network_api.server.RequestAnalyser.BadRequestException.class, + c3 = fr.pandacube.util.network_api.server.Response.class, + c4 = fr.pandacube.util.text_display.ChatUtil.class; + } } diff --git a/src/main/java/fr/pandacube/util/AmountPerTimeLimiter.java b/src/main/java/fr/pandacube/util/AmountPerTimeLimiter.java new file mode 100644 index 0000000..8c5b687 --- /dev/null +++ b/src/main/java/fr/pandacube/util/AmountPerTimeLimiter.java @@ -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 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()); + } +} diff --git a/src/main/java/fr/pandacube/util/MappedListView.java b/src/main/java/fr/pandacube/util/MappedListView.java new file mode 100644 index 0000000..745bdea --- /dev/null +++ b/src/main/java/fr/pandacube/util/MappedListView.java @@ -0,0 +1,53 @@ +package fr.pandacube.util; + +import java.util.AbstractList; +import java.util.List; +import java.util.function.Function; + +public class MappedListView extends AbstractList { + + private final List backend; + private final Function getter; + private final Function 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 backend, Function getter, Function 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 subList(int fromIndex, int toIndex) { + return new MappedListView(backend.subList(fromIndex, toIndex), getter, setter); + } + @Override + protected void removeRange(int fromIndex, int toIndex) { + backend.subList(fromIndex, toIndex).clear(); + } + + + +} diff --git a/src/main/java/fr/pandacube/util/MinecraftVersion.java b/src/main/java/fr/pandacube/util/MinecraftVersion.java index 1a4dd07..0d508f0 100644 --- a/src/main/java/fr/pandacube/util/MinecraftVersion.java +++ b/src/main/java/fr/pandacube/util/MinecraftVersion.java @@ -34,7 +34,12 @@ public enum MinecraftVersion { v1_14_4(498, "1.14.4"), v1_15(573, "1.15"), v1_15_1(575, "1.15.1"), - v1_15_2(578, "1.15.2"); + v1_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; private static Map, List> versionMergeDisplay; @@ -102,14 +107,35 @@ public enum MinecraftVersion { ImmutableList.of("1.15", "1.15.1")); versionMergeDisplay.put(EnumSet.of(v1_15_1, v1_15_2), ImmutableList.of("1.15.1", "1.15.2")); + + versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2, v1_16_3, v1_16_4), + 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 versionDisplay; private MinecraftVersion(int v, String... d) { - versionNumber = v; + id = v; versionDisplay = Arrays.asList(d); } @@ -120,19 +146,25 @@ public enum MinecraftVersion { public static MinecraftVersion getVersion(int v) { for (MinecraftVersion mcV : values()) - if (mcV.versionNumber == v) return mcV; + if (mcV.id == v) return mcV; return null; } - public static String displayOptimizedListOfVersions(List versions) { + public static String displayOptimizedListOfVersionsAnd(List versions) { + return StringUtil.joinGrammatically(", ", " et ", getVersionsDisplayList(versions)); + } + + public static String displayOptimizedListOfVersionsOr(List versions) { return StringUtil.joinGrammatically(", ", " et ", getVersionsDisplayList(versions)); } public static final List getVersionsDisplayList(List vList) { + if (vList == null) + return new ArrayList<>(); Set vSet = EnumSet.copyOf(vList); List ret = new ArrayList<>(); diff --git a/src/main/java/fr/pandacube/util/RandomUtil.java b/src/main/java/fr/pandacube/util/RandomUtil.java index 5e7544c..19e436c 100644 --- a/src/main/java/fr/pandacube/util/RandomUtil.java +++ b/src/main/java/fr/pandacube/util/RandomUtil.java @@ -17,11 +17,11 @@ public class RandomUtil { } public static 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 listElement(List arr) { - return arr.get(rand.nextInt(arr.size())); + return (arr == null || arr.isEmpty()) ? null : arr.get(rand.nextInt(arr.size())); } /** diff --git a/src/main/java/fr/pandacube/util/StringUtil.java b/src/main/java/fr/pandacube/util/StringUtil.java index e917395..b9f6faa 100644 --- a/src/main/java/fr/pandacube/util/StringUtil.java +++ b/src/main/java/fr/pandacube/util/StringUtil.java @@ -33,8 +33,23 @@ public class StringUtil { - + 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); } } diff --git a/src/main/java/fr/pandacube/util/commands/AbstractCommand.java b/src/main/java/fr/pandacube/util/commands/AbstractCommand.java index 9333187..a208973 100644 --- a/src/main/java/fr/pandacube/util/commands/AbstractCommand.java +++ b/src/main/java/fr/pandacube/util/commands/AbstractCommand.java @@ -2,7 +2,9 @@ package fr.pandacube.util.commands; import java.util.Arrays; -public class AbstractCommand { +import fr.pandacube.util.text_display.ChatStatic; + +public class AbstractCommand extends ChatStatic { public final String commandName; diff --git a/src/main/java/fr/pandacube/util/commands/SuggestionsSupplier.java b/src/main/java/fr/pandacube/util/commands/SuggestionsSupplier.java index 0701a52..8ff66e0 100644 --- a/src/main/java/fr/pandacube/util/commands/SuggestionsSupplier.java +++ b/src/main/java/fr/pandacube/util/commands/SuggestionsSupplier.java @@ -6,6 +6,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -47,13 +48,20 @@ public interface SuggestionsSupplier { public static SuggestionsSupplier empty() { return (s, ti, t, a) -> Collections.emptyList(); } + public static SuggestionsSupplier fromCollectionsSupplier(Supplier> streamSupplier) { + return (s, ti, token, a) -> collectFilteredStream(streamSupplier.get().stream(), token); + } + + public static SuggestionsSupplier fromStreamSupplier(Supplier> streamSupplier) { + return (s, ti, token, a) -> collectFilteredStream(streamSupplier.get(), token); + } public static SuggestionsSupplier fromCollection(Collection suggestions) { - return (s, ti, token, a) -> collectFilteredStream(suggestions.stream(), token); + return fromStreamSupplier(suggestions::stream); } public static SuggestionsSupplier 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 { }; } + + + public static SuggestionsSupplier booleanValues() { + return fromCollection(Arrays.asList("true", "false")); + } + + + + /** * Create a {@link SuggestionsSupplier} that suggest numbers according to the provided range. @@ -167,9 +184,11 @@ public interface 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 * @return */ @@ -206,11 +225,121 @@ public interface SuggestionsSupplier { + + + public default SuggestionsSupplier 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 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 requires(Predicate check) { return (s, ti, to, a) -> { return check.test(s) ? getSuggestions(s, ti, to, a) : Collections.emptyList(); }; } + + + /** + * Returns a new {@link SuggestionsSupplier} containing all the element of this instance then the element of the provided one, + * with all duplicated values removed using {@link Stream#distinct()}. + * @param other + * @return + */ + public default SuggestionsSupplier merge(SuggestionsSupplier other) { + return (s, ti, to, a) -> { + List l1 = getSuggestions(s, ti, to, a); + List 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 orIfEmpty(SuggestionsSupplier other) { + return (s, ti, to, a) -> { + List l1 = getSuggestions(s, ti, to, a); + return !l1.isEmpty() ? l1 : other.getSuggestions(s, ti, to, a); + }; + } + } \ No newline at end of file diff --git a/src/main/java/fr/pandacube/util/config/AbstractConfig.java b/src/main/java/fr/pandacube/util/config/AbstractConfig.java index 973b807..20b2534 100644 --- a/src/main/java/fr/pandacube/util/config/AbstractConfig.java +++ b/src/main/java/fr/pandacube/util/config/AbstractConfig.java @@ -10,7 +10,7 @@ import java.util.Collections; import java.util.List; 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é * @author Marc Baloup @@ -125,7 +125,7 @@ public abstract class AbstractConfig { public static String getTranslatedColorCode(String string) { - return ChatColor.translateAlternateColorCodes('&', string); + return ChatColorUtil.translateAlternateColorCodes('&', string); } diff --git a/src/main/java/fr/pandacube/util/measurement/TimeUtil.java b/src/main/java/fr/pandacube/util/measurement/TimeUtil.java index 551d5cc..ad52218 100644 --- a/src/main/java/fr/pandacube/util/measurement/TimeUtil.java +++ b/src/main/java/fr/pandacube/util/measurement/TimeUtil.java @@ -1,54 +1,262 @@ package fr.pandacube.util.measurement; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; +import java.util.Collections; import java.util.GregorianCalendar; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import fr.pandacube.Pandacube; +import fr.pandacube.util.StringUtil; +import fr.pandacube.util.commands.SuggestionsSupplier; public class TimeUtil { - public static String durationToString(long msec_time, boolean dec_seconde) { - boolean neg = msec_time < 0; - msec_time = Math.abs(msec_time); - int j = 0, h = 0, m = 0, s = 0; - long msec = msec_time; + - j = (int) (msec / (1000 * 60 * 60 * 24)); - msec -= (long) (1000 * 60 * 60 * 24) * j; - h = (int) (msec / (1000 * 60 * 60)); - msec -= (long) (1000 * 60 * 60) * h; - m = (int) (msec / (1000 * 60)); - msec -= (long) (1000 * 60) * m; - s = (int) (msec / 1000); - msec -= (long) 1000 * s; + private static DateTimeFormatter cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Pandacube.LOCALE); + private static DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Pandacube.LOCALE); + private static DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Pandacube.LOCALE); + private static DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Pandacube.LOCALE); + private static DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Pandacube.LOCALE); + private static DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Pandacube.LOCALE); - String result = ""; - if (j > 0) result = result.concat(j + "j "); - if (h > 0) result = result.concat(h + "h "); - if (m > 0) result = result.concat(m + "m "); - if (s > 0 && !dec_seconde) result = result.concat(s + "s"); - else if (dec_seconde && (s > 0 || msec > 0)) { - msec += s * 1000; - result = result.concat((msec / 1000D) + "s"); - } - - if (result.equals("")) result = "0"; - result = result.trim(); - if (neg) - result = "-" + result; + private static DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Pandacube.LOCALE); + private static DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Pandacube.LOCALE); + + + + + public static String relativeDateFr(long displayTime, boolean showSeconds, boolean compactWords) { + long currentTime = System.currentTimeMillis(); + + + LocalDateTime displayDateTime = toLocalDateTime(displayTime); + LocalDateTime currentDateTime = toLocalDateTime(currentTime); + + + long timeDiff = currentTime - displayTime; + long timeDiffSec = timeDiff / 1000; + + if (timeDiffSec < -1) { + // in the future + if (timeDiffSec > -60) { + if (showSeconds) + return "dans " + (-timeDiffSec) + " secondes"; + else + return "dans moins d’une minute"; + } + if (timeDiffSec > -60*2) // dans 2 min + return "dans ̈" + (int)Math.floor((-timeDiffSec)/60) + " minute"; + if (timeDiffSec > -3600) // dans 1h + return "dans " + (int)Math.floor((-timeDiffSec)/60) + " minutes"; + if (timeDiffSec > -3600*2) // dans 2h + return "dans " + (int)Math.floor((-timeDiffSec)/(3600)) + " heure"; + if (timeDiffSec > -3600*12) // dans 12h + return "dans " + (int)Math.floor((-timeDiffSec)/(3600)) + " heures"; - return result; + LocalDateTime nextMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0).plusDays(1); + if (displayDateTime.isBefore(nextMidnight)) // aujourd'hui + return "aujourd’hui à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime); + if (displayDateTime.isBefore(nextMidnight.plusDays(1))) // demain + return "demain à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime); + if (displayDateTime.isBefore(nextMidnight.plusDays(5))) // dans moins d'1 semaine + return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " " + + dayOfMonthFormatter.format(displayDateTime) + " à " + + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime); + + return fullDateFr(displayTime, showSeconds, true, compactWords); + + } + else { + // present and past + if (timeDiffSec <= 1) + return "maintenant"; + if (timeDiffSec < 60) { // ya moins d'1 min + if (showSeconds) + return "il y a " + timeDiffSec + " secondes"; + else + return "il y a moins d’une minute"; + } + if (timeDiffSec < 60*2) // ya moins de 2 min + return "il y a " + (int)Math.floor((timeDiffSec)/60) + " minute"; + if (timeDiffSec < 3600) // ya moins d'1h + return "il y a " + (int)Math.floor((timeDiffSec)/60) + " minutes"; + if (timeDiffSec < 3600*2) // ya moins de 2h + return "il y a " + (int)Math.floor((timeDiffSec)/(3600)) + " heure"; + if (timeDiffSec < 3600*12) // ya moins de 12h + return "il y a " + (int)Math.floor((timeDiffSec)/(3600)) + " heures"; + + LocalDateTime lastMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0); + + if (!displayDateTime.isBefore(lastMidnight)) // aujourd'hui + return "aujourd’hui à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime); + if (!displayDateTime.isBefore(lastMidnight.minusDays(1))) // hier + return "hier à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime); + if (!displayDateTime.isBefore(lastMidnight.minusDays(6))) // ya moins d'1 semaine + return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " dernier à " + + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime); + + return fullDateFr(displayTime, showSeconds, true, compactWords); + + } + + } + + public static String fullDateFr(long displayTime, boolean showSeconds, boolean showWeekday, boolean compactWords) { + LocalDateTime displayDateTime = toLocalDateTime(displayTime); + return (showWeekday ? ((compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " ") : "") + + dayOfMonthFormatter.format(displayDateTime) + " " + + (compactWords ? cmpMonthFormatter : monthFormatter).format(displayDateTime) + " " + + yearFormatter.format(displayDateTime) + " à " + + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime); + } + + + private static LocalDateTime toLocalDateTime(long msTime) { + return Instant.ofEpochMilli(msTime).atZone(Pandacube.TIMEZONE.toZoneId()).toLocalDateTime(); + } + + + + + + + + + + + public static String durationToLongString(long msDuration, TimeUnit hUnit, TimeUnit lUnit, boolean spaces, boolean fr, boolean leadingZeros) { + if (lUnit.compareTo(hUnit) > 0) { + TimeUnit tmp = lUnit; + lUnit = hUnit; + hUnit = tmp; + } + if (lUnit.compareTo(TimeUnit.MILLISECONDS) < 0) + lUnit = TimeUnit.MILLISECONDS; + if (hUnit.compareTo(TimeUnit.MILLISECONDS) < 0) + hUnit = TimeUnit.MILLISECONDS; + + + AtomicLong remainingTime = new AtomicLong(msDuration); + AtomicBoolean oneDisplayed = new AtomicBoolean(false); + final TimeUnit fLUnit = lUnit, fHUnit = hUnit; + + String ret = Arrays.stream(TimeUnit.values()) + .sequential() + .filter(u -> u.compareTo(fLUnit) >= 0 && u.compareTo(fHUnit) <= 0) + .sorted((u1, u2) -> u2.compareTo(u1)) + .filter(u -> { + if (u.convert(remainingTime.get(), TimeUnit.MILLISECONDS) == 0 && !oneDisplayed.get()) + return false; + oneDisplayed.set(true); + return true; + }) + .map(u -> { + long v = u.convert(remainingTime.get(), TimeUnit.MILLISECONDS); + remainingTime.addAndGet(TimeUnit.MILLISECONDS.convert(-v, u)); + return toString(v, leadingZeros ? timeUnitToLeftPadLength(u) : 1) + timeUnitToSuffix(u, fr); + }) + .collect(Collectors.joining(spaces ? " " : "")); + // ensure there is at least something to display (for instance : "0s") + return oneDisplayed.get() ? ret : (toString(0, leadingZeros ? timeUnitToLeftPadLength(lUnit) : 1) + timeUnitToSuffix(lUnit, fr)); + } + + + public static String timeUnitToSuffix(TimeUnit u, boolean fr) { + switch (u) { + case DAYS: + return fr ? "j" : "d"; + case HOURS: + return "h"; + case MINUTES: + return "m"; + case SECONDS: + return "s"; + case MILLISECONDS: + return "ms"; + case MICROSECONDS: + return "μs"; + case NANOSECONDS: + return "ns"; + default: + throw new IllegalArgumentException("Invalid TimeUnit: " + Objects.toString(u)); + } + } + + public static int timeUnitToLeftPadLength(TimeUnit u) { + switch (u) { + case NANOSECONDS: + case MICROSECONDS: + case MILLISECONDS: + return 3; + case SECONDS: + case MINUTES: + case HOURS: + return 2; + case DAYS: + default: + return 1; + } + } + + public static String toString(long value, int leftPad) { + String valueStr = Long.toString(value); + int padding = leftPad - valueStr.length(); + if (padding <= 0) + return valueStr; + return StringUtil.repeat("0", padding) + valueStr; + } + + + + + + + /** + * Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false)} + * @param msDuration the duration in ms + * @param milliseconds if the milliseconds are displayed or not + * @return + */ + public static String durationToString(long msDuration, boolean milliseconds) { + return durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false); } - 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]*)?" + "(?:([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]*)?" @@ -94,5 +302,52 @@ public class TimeUtil { if (c.after(max)) return max.getTimeInMillis(); return c.getTimeInMillis(); } + + + + + + public static SuggestionsSupplier suggestDuration() { + return (s, ti, token, args) -> { + if (token.isEmpty()) { + return emptyTokenSuggestions; + } + List 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 allSuffixes = Arrays.asList("y", "mo", "w", "d", "h", "m", "s"); + private static List emptyTokenSuggestions = allSuffixes.stream().map(p -> "1" + p).collect(Collectors.toList()); + private static void scanAndRemovePastSuffixes(List 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; + } + } + } + } diff --git a/src/main/java/fr/pandacube/util/orm/SQLCustomType.java b/src/main/java/fr/pandacube/util/orm/SQLCustomType.java index 2ad7e74..34bd9ec 100644 --- a/src/main/java/fr/pandacube/util/orm/SQLCustomType.java +++ b/src/main/java/fr/pandacube/util/orm/SQLCustomType.java @@ -3,9 +3,6 @@ package fr.pandacube.util.orm; import java.util.function.Function; /** - * - * @author Marc - * * @param intermediate type, the type of the value transmitted to the JDBC * @param Java type */ @@ -15,18 +12,14 @@ public class SQLCustomType extends SQLType { public final Function dbToJavaConv; public final Function javaToDbConv; - protected SQLCustomType(SQLType type, Class javaT, Function dbToJava, Function javaToDb) { + /* package */ SQLCustomType(SQLType type, Class javaT, Function dbToJava, Function javaToDb) { this(type.sqlDeclaration, type.getJavaType(), javaT, dbToJava, javaToDb); } - protected SQLCustomType(String sqlD, Class intermediateJavaT, Class javaT, Function dbToJava, Function javaToDb) { + /* package */ SQLCustomType(String sqlD, Class intermediateJavaT, Class javaT, Function dbToJava, Function javaToDb) { super(sqlD, javaT); intermediateJavaType = intermediateJavaT; dbToJavaConv = dbToJava; javaToDbConv = javaToDb; } - - - // TODO tester en local - } diff --git a/src/main/java/fr/pandacube/util/orm/SQLElement.java b/src/main/java/fr/pandacube/util/orm/SQLElement.java index 1fb224b..46bb32e 100644 --- a/src/main/java/fr/pandacube/util/orm/SQLElement.java +++ b/src/main/java/fr/pandacube/util/orm/SQLElement.java @@ -1,6 +1,7 @@ package fr.pandacube.util.orm; import java.lang.reflect.Modifier; +import java.sql.Date; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -14,12 +15,14 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.UUID; import org.apache.commons.lang.builder.ToStringBuilder; import com.google.gson.Gson; import com.google.gson.JsonObject; +import fr.pandacube.util.EnumUtil; import fr.pandacube.util.Log; public abstract class SQLElement> { @@ -49,7 +52,7 @@ public abstract class SQLElement> { fields = new SQLFieldMap<>((Class)getClass()); // le champ id commun à toutes les tables - SQLField idF = new SQLField<>(SQLType.INT, false, true, 0); + SQLField idF = new SQLField<>(INT, false, true, 0); idF.setName("id"); fields.addField(idF); @@ -133,8 +136,10 @@ public abstract class SQLElement> { return Collections.unmodifiableMap(values); } - public void set(SQLField field, T value) { + @SuppressWarnings("unchecked") + public E set(SQLField field, T value) { set(field, value, true); + return (E) this; } /* package */ void set(SQLField sqlField, T value, boolean setModified) { @@ -220,7 +225,7 @@ public abstract class SQLElement> { } @SuppressWarnings("unchecked") - public void save() throws ORMException { + public E save() throws ORMException { if (!isValidForSave()) throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved."); @@ -236,7 +241,7 @@ public abstract class SQLElement> { modifiedSinceLastSave.remove("id"); Map, Object> modifiedValues = getOnlyModifiedValues(); - if (modifiedValues.isEmpty()) return; + if (modifiedValues.isEmpty()) return (E) this; ORM.update((Class)getClass(), getFieldId().eq(getId()), modifiedValues); } @@ -284,6 +289,7 @@ public abstract class SQLElement> { } catch (SQLException e) { throw new ORMException("Error while saving data", e); } + return (E) this; } @@ -396,5 +402,100 @@ public abstract class SQLElement> { } return json; } + + + + + + + + + + + protected static , T> SQLField field(SQLType t, boolean nul, boolean autoIncr, T deflt) { + return new SQLField<>(t, nul, autoIncr, deflt); + } + + protected static , T> SQLField field(SQLType t, boolean nul) { + return new SQLField<>(t, nul); + } + + protected static , T> SQLField field(SQLType t, boolean nul, boolean autoIncr) { + return new SQLField<>(t, nul, autoIncr); + } + + protected static , T> SQLField field(SQLType t, boolean nul, T deflt) { + return new SQLField<>(t, nul, deflt); + } + + + protected static , F extends SQLElement> SQLFKField foreignKeyId(boolean nul, Class fkEl) { + return SQLFKField.idFK(nul, fkEl); + } + + protected static , F extends SQLElement> SQLFKField foreignKeyId(boolean nul, Integer deflt, Class fkEl) { + return SQLFKField.idFK(nul, deflt, fkEl); + } + + protected static , T, F extends SQLElement> SQLFKField foreignKey(boolean nul, Class fkEl, SQLField fkF) { + return SQLFKField.customFK(nul, fkEl, fkF); + } + + protected static , T, F extends SQLElement> SQLFKField foreignKey(boolean nul, T deflt, Class fkEl, SQLField fkF) { + return SQLFKField.customFK(nul, deflt, fkEl, fkF); + } + + + public static final SQLType BOOLEAN = new SQLType<>("BOOLEAN", Boolean.class); + + public static final SQLType TINYINT = new SQLType<>("TINYINT", Integer.class); // can’t be Byte due to MYSQL JDBC Connector limitations + public static final SQLType BYTE = TINYINT; + + public static final SQLType SMALLINT = new SQLType<>("SMALLINT", Integer.class); // can’t be Short due to MYSQL JDBC Connector limitations + public static final SQLType SHORT = SMALLINT; + + public static final SQLType INT = new SQLType<>("INT", Integer.class); + public static final SQLType INTEGER = INT; + + public static final SQLType BIGINT = new SQLType<>("BIGINT", Long.class); + public static final SQLType LONG = BIGINT; + + public static final SQLType DATE = new SQLType<>("DATE", Date.class); + + public static final SQLType FLOAT = new SQLType<>("FLOAT", Float.class); + + public static final SQLType DOUBLE = new SQLType<>("DOUBLE", Double.class); + + @Deprecated + public static final SQLType CHAR(int charCount) { + if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive."); + return new SQLType<>("CHAR(" + charCount + ")", String.class); + } + + public static final SQLType VARCHAR(int charCount) { + if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive."); + return new SQLType<>("VARCHAR(" + charCount + ")", String.class); + } + + public static final SQLType TEXT = new SQLType<>("TEXT", String.class); + public static final SQLType STRING = TEXT; + + public static final > SQLType ENUM(Class 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 CHAR36_UUID = new SQLCustomType<>(CHAR(36), UUID.class, UUID::fromString, UUID::toString); + } diff --git a/src/main/java/fr/pandacube/util/orm/SQLElementList.java b/src/main/java/fr/pandacube/util/orm/SQLElementList.java index 1bbc8c4..95ff0f2 100644 --- a/src/main/java/fr/pandacube/util/orm/SQLElementList.java +++ b/src/main/java/fr/pandacube/util/orm/SQLElementList.java @@ -1,6 +1,5 @@ package fr.pandacube.util.orm; -import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; @@ -106,31 +105,25 @@ public class SQLElementList> extends 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() { List storedEl = getStoredEl(); if (storedEl.isEmpty()) return; try { - - String sqlWhere = ""; - boolean first = true; - for (E el : storedEl) { - if (!first) sqlWhere += " OR "; - first = false; - sqlWhere += "id = " + el.getId(); - } - - try (PreparedStatement st = ORM.getConnection().getNativeConnection() - .prepareStatement("DELETE FROM " + storedEl.get(0).tableName() + " WHERE " + sqlWhere)) { - Log.debug(st.toString()); - st.executeUpdate(); - - for (E el : storedEl) - el.markAsNotStored(); - - } - - } catch (SQLException e) { + @SuppressWarnings("unchecked") + Class classEl = (Class)storedEl.get(0).getClass(); + + ORM.delete(classEl, + storedEl.get(0).getFieldId().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList())) + ); + for (E el : storedEl) + el.markAsNotStored(); + } catch (ORMException e) { Log.severe(e); } @@ -150,11 +143,7 @@ public class SQLElementList> extends ArrayList { return new SQLElementList<>(); } - SQLWhereOr

where = SQLWhere.or(); - values.forEach(v -> where.or(foreignKey.getPrimaryField().eq(v))); - - - return ORM.getAll(foreignKey.getForeignElementClass(), where, orderBy, null, null); + return ORM.getAll(foreignKey.getForeignElementClass(), foreignKey.getPrimaryField().in(values), orderBy, null, null); } @@ -180,11 +169,8 @@ public class SQLElementList> extends ArrayList { if (values.isEmpty()) { return new SQLElementList<>(); } - - SQLWhereOr 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); } diff --git a/src/main/java/fr/pandacube/util/orm/SQLFKField.java b/src/main/java/fr/pandacube/util/orm/SQLFKField.java index 9209a9a..080c398 100644 --- a/src/main/java/fr/pandacube/util/orm/SQLFKField.java +++ b/src/main/java/fr/pandacube/util/orm/SQLFKField.java @@ -20,11 +20,11 @@ public class SQLFKField, T, P extends SQLElement

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

> ext } } - public static , T, F extends SQLElement> SQLFKField customFK(boolean nul, Class fkEl, SQLField fkF) { + /* package */ static , T, F extends SQLElement> SQLFKField customFK(boolean nul, Class fkEl, SQLField fkF) { return customFK(nul, null, fkEl, fkF); } - public static , T, F extends SQLElement> SQLFKField customFK(boolean nul, T deflt, Class fkEl, SQLField fkF) { + /* package */ static , T, F extends SQLElement> SQLFKField customFK(boolean nul, T deflt, Class fkEl, SQLField fkF) { if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null"); return new SQLFKField<>(fkF.type, nul, deflt, fkEl, fkF); } diff --git a/src/main/java/fr/pandacube/util/orm/SQLField.java b/src/main/java/fr/pandacube/util/orm/SQLField.java index 2a53907..6096a16 100644 --- a/src/main/java/fr/pandacube/util/orm/SQLField.java +++ b/src/main/java/fr/pandacube/util/orm/SQLField.java @@ -17,22 +17,22 @@ public class SQLField, T> { public final boolean autoIncrement; /* package */ final T defaultValue; - public SQLField(SQLType t, boolean nul, boolean autoIncr, T deflt) { + /* package */ SQLField(SQLType t, boolean nul, boolean autoIncr, T deflt) { type = t; canBeNull = nul; autoIncrement = autoIncr; defaultValue = deflt; } - public SQLField(SQLType t, boolean nul) { + /* package */ SQLField(SQLType t, boolean nul) { this(t, nul, false, null); } - public SQLField(SQLType t, boolean nul, boolean autoIncr) { + /* package */ SQLField(SQLType t, boolean nul, boolean autoIncr) { this(t, nul, autoIncr, null); } - public SQLField(SQLType t, boolean nul, T deflt) { + /* package */ SQLField(SQLType t, boolean nul, T deflt) { this(t, nul, false, deflt); } @@ -112,10 +112,17 @@ public class SQLField, T> { } private SQLWhere comp(SQLComparator c, T r) { + if (r == null) + throw new IllegalArgumentException("The value cannot be null. Use SQLField#isNull(value) or SQLField#isNotNull(value) to check for null values"); return new SQLWhereComp<>(this, c, r); } + public SQLWhere like(String like) { + return new SQLWhereLike<>(this, like); + } + + public SQLWhere in(Collection v) { return new SQLWhereIn<>(this, v); diff --git a/src/main/java/fr/pandacube/util/orm/SQLType.java b/src/main/java/fr/pandacube/util/orm/SQLType.java index 80420dd..544aab8 100644 --- a/src/main/java/fr/pandacube/util/orm/SQLType.java +++ b/src/main/java/fr/pandacube/util/orm/SQLType.java @@ -1,16 +1,11 @@ package fr.pandacube.util.orm; -import java.sql.Date; -import java.util.UUID; - -import fr.pandacube.util.EnumUtil; - public class SQLType { protected final String sqlDeclaration; private final Class javaTypes; - protected SQLType(String sqlD, Class javaT) { + /* package */ SQLType(String sqlD, Class javaT) { sqlDeclaration = sqlD; javaTypes = javaT; } @@ -40,56 +35,5 @@ public class SQLType { return javaTypes; } - public static final SQLType BOOLEAN = new SQLType<>("BOOLEAN", Boolean.class); - - public static final SQLType TINYINT = new SQLType<>("TINYINT", Byte.class); - public static final SQLType BYTE = TINYINT; - - public static final SQLType SMALLINT = new SQLType<>("SMALLINT", Short.class); - public static final SQLType SHORT = SMALLINT; - - public static final SQLType INT = new SQLType<>("INT", Integer.class); - public static final SQLType INTEGER = INT; - - public static final SQLType BIGINT = new SQLType<>("BIGINT", Long.class); - public static final SQLType LONG = BIGINT; - - public static final SQLType DATE = new SQLType<>("DATE", Date.class); - - public static final SQLType FLOAT = new SQLType<>("FLOAT", Float.class); - - public static final SQLType DOUBLE = new SQLType<>("DOUBLE", Double.class); - @Deprecated - public static final SQLType CHAR(int charCount) { - if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive."); - return new SQLType<>("CHAR(" + charCount + ")", String.class); - } - - public static final SQLType VARCHAR(int charCount) { - if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive."); - return new SQLType<>("VARCHAR(" + charCount + ")", String.class); - } - - public static final SQLType TEXT = new SQLType<>("TEXT", String.class); - public static final SQLType STRING = TEXT; - - public static final > SQLType ENUM(Class 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 CHAR36_UUID = new SQLCustomType<>(SQLType.CHAR(36), UUID.class, UUID::fromString, UUID::toString); - } diff --git a/src/main/java/fr/pandacube/util/orm/SQLWhere.java b/src/main/java/fr/pandacube/util/orm/SQLWhere.java index 74641b3..997842c 100644 --- a/src/main/java/fr/pandacube/util/orm/SQLWhere.java +++ b/src/main/java/fr/pandacube/util/orm/SQLWhere.java @@ -36,10 +36,8 @@ public abstract class SQLWhere> { return new SQLWhereOr<>(); } - - - public static > SQLWhere like(SQLField f, String like) { - return new SQLWhereLike(f, like); + public static String escapeLike(String str) { + return str.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%"); } } diff --git a/src/main/java/fr/pandacube/util/orm/SQLWhereComp.java b/src/main/java/fr/pandacube/util/orm/SQLWhereComp.java index bbe481e..15543c9 100644 --- a/src/main/java/fr/pandacube/util/orm/SQLWhereComp.java +++ b/src/main/java/fr/pandacube/util/orm/SQLWhereComp.java @@ -7,7 +7,7 @@ import org.javatuples.Pair; /* package */ class SQLWhereComp> extends SQLWhere { - private SQLField left; + private SQLField left; private SQLComparator comp; private Object right; @@ -18,7 +18,7 @@ import org.javatuples.Pair; * @param c the comparison operator, can't be null * @param r the value at right of the comparison operator. Can't be null */ - /* package */ SQLWhereComp(SQLField l, SQLComparator c, T r) { + /* package */ SQLWhereComp(SQLField l, SQLComparator c, T r) { if (l == null || r == null || c == null) throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null"); left = l; diff --git a/src/main/java/fr/pandacube/util/orm/SQLWhereLike.java b/src/main/java/fr/pandacube/util/orm/SQLWhereLike.java index 85dde1f..ab5a349 100644 --- a/src/main/java/fr/pandacube/util/orm/SQLWhereLike.java +++ b/src/main/java/fr/pandacube/util/orm/SQLWhereLike.java @@ -7,7 +7,7 @@ import org.javatuples.Pair; /* package */ class SQLWhereLike> extends SQLWhere { - private SQLField field; + private SQLField field; 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 like the like expression. */ - /* package */ SQLWhereLike(SQLField f, String like) { + /* package */ SQLWhereLike(SQLField f, String like) { if (f == null || like == null) throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null"); field = f; diff --git a/src/main/java/fr/pandacube/util/text_display/Chat.java b/src/main/java/fr/pandacube/util/text_display/Chat.java new file mode 100644 index 0000000..2451a8d --- /dev/null +++ b/src/main/java/fr/pandacube/util/text_display/Chat.java @@ -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; + } + +} diff --git a/src/main/java/fr/pandacube/util/text_display/ChatColorUtil.java b/src/main/java/fr/pandacube/util/text_display/ChatColorUtil.java new file mode 100644 index 0000000..b10c3bb --- /dev/null +++ b/src/main/java/fr/pandacube/util/text_display/ChatColorUtil.java @@ -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. + *

+ * 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)}). + *

+ * 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> 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); + } + } + +} \ No newline at end of file diff --git a/src/main/java/fr/pandacube/util/text_display/ChatStatic.java b/src/main/java/fr/pandacube/util/text_display/ChatStatic.java new file mode 100644 index 0000000..3708e6d --- /dev/null +++ b/src/main/java/fr/pandacube/util/text_display/ChatStatic.java @@ -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)); + } +} diff --git a/src/main/java/fr/pandacube/util/text_display/DisplayUtil.java b/src/main/java/fr/pandacube/util/text_display/ChatUtil.java similarity index 50% rename from src/main/java/fr/pandacube/util/text_display/DisplayUtil.java rename to src/main/java/fr/pandacube/util/text_display/ChatUtil.java index 2a9b97b..874d5dc 100644 --- a/src/main/java/fr/pandacube/util/text_display/DisplayUtil.java +++ b/src/main/java/fr/pandacube/util/text_display/ChatUtil.java @@ -1,5 +1,10 @@ package fr.pandacube.util.text_display; +import static fr.pandacube.util.text_display.Chat.chatComponent; +import static fr.pandacube.util.text_display.ChatStatic.chat; +import static fr.pandacube.util.text_display.ChatStatic.legacyText; +import static fr.pandacube.util.text_display.ChatStatic.text; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -9,12 +14,14 @@ import java.util.TreeSet; import com.google.common.collect.ImmutableMap; +import fr.pandacube.Pandacube; +import fr.pandacube.util.text_display.Chat.FormatableChat; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TranslatableComponent; -public class DisplayUtil { +public class ChatUtil { public static final int DEFAULT_CHAR_SIZE = 6; public static final Map CHARS_SIZE = new ImmutableMap.Builder() @@ -34,33 +41,31 @@ public class DisplayUtil { public static final int BOOK_WIDTH = 116; public static final int CONSOLE_NB_CHAR_DEFAULT = 50; + + + + + + - public static final ChatColor COLOR_TITLE = ChatColor.GOLD; - public static final ChatColor COLOR_LINK = ChatColor.GREEN; - public static final ChatColor COLOR_COMMAND = ChatColor.GRAY; - - - - - - + public static BaseComponent createURLLink(String text, String url) { + return createURLLink(legacyText(text), url, null); + } public static BaseComponent createURLLink(String text, String url, String hoverText) { - return _createURLLink(new Display(text), url, hoverText); - } - public static BaseComponent createURLLink(BaseComponent text, String url, String hoverText) { - return _createURLLink(new Display(text), url, hoverText); - } - public static BaseComponent createURLLink(BaseComponent[] text, String url, String hoverText) { - return _createURLLink(new Display(text), url, hoverText); + return createURLLink(legacyText(text), url, hoverText != null ? legacyText(hoverText) : null); } - 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; - return d.clickURL(url) - .hoverText(ChatColor.GRAY + ((hoverText == null) ? "Cliquez pour accéder au site :" : hoverText) + "\n" - + ChatColor.GRAY + dispURL) - .color(COLOR_LINK).get(); + return chat() + .clickURL(url) + .color(Pandacube.CHAT_URL_COLOR) + .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) { - 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) { - return createCommandLink(text, commandWithSlash, hoverText == null ? null : new BaseComponent[] {hoverText}); - } - public static BaseComponent createCommandLink(String text, String commandWithSlash, BaseComponent[] hoverText) { - return _createCommandLink(new Display(text), commandWithSlash, hoverText); + public static BaseComponent createCommandLink(String text, String commandWithSlash, Chat hoverText) { + return createCommandLink(legacyText(text), commandWithSlash, hoverText); } - private static BaseComponent _createCommandLink(Display d, String commandWithSlash, BaseComponent[] hoverText) { - d.clickCommand(commandWithSlash).color(COLOR_COMMAND); - if (hoverText != null) d.hoverText(hoverText); - return d.get(); + /* package */ static BaseComponent createCommandLink(Chat d, String commandWithSlash, Chat hoverText) { + FormatableChat c = chat() + .clickCommand(commandWithSlash) + .color(Pandacube.CHAT_COMMAND_COLOR); + if (hoverText != null) + c.hoverText(hoverText); + return c.then(d).get(); } @@ -96,19 +101,19 @@ public class DisplayUtil { 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) { - return createCommandSuggest(text, commandWithSlash, hoverText == null ? null : new BaseComponent[] {hoverText}); - } - public static BaseComponent createCommandSuggest(String text, String commandWithSlash, BaseComponent[] hoverText) { - return _createCommandSuggest(new Display(text), commandWithSlash, hoverText); + public static BaseComponent createCommandSuggest(String text, String commandWithSlash, Chat hoverText) { + return createCommandSuggest(legacyText(text), commandWithSlash, hoverText); } - private static BaseComponent _createCommandSuggest(Display d, String commandWithSlash, BaseComponent[] hoverText) { - d.clickSuggest(commandWithSlash).color(COLOR_COMMAND); - if (hoverText != null) d.hoverText(hoverText); - return d.get(); + /* package */ static BaseComponent createCommandSuggest(Chat d, String commandWithSlash, Chat hoverText) { + FormatableChat c = chat() + .clickSuggest(commandWithSlash) + .color(Pandacube.CHAT_COMMAND_COLOR); + if (hoverText != null) + c.hoverText(hoverText); + return c.then(d).get(); } @@ -134,32 +139,33 @@ public class DisplayUtil { pagesToDisplay.add(i); } - Display d = new Display(prefix); + Chat d = chat().thenLegacyText(prefix); boolean first = true; int previous = 0; for (int page : pagesToDisplay) { if (!first) { if (page == previous + 1) { - d.next(" "); + d.thenText(" "); } else { if (cmdFormat.endsWith("%d")) { - d.next(" "); - d.next(createCommandSuggest("...", cmdFormat.substring(0, cmdFormat.length() - 2), "Choisir la page")); - d.next(" "); + d.thenText(" "); + d.then(createCommandSuggest("...", cmdFormat.substring(0, cmdFormat.length() - 2), "Choisir la page")); + d.thenText(" "); } else - d.next(" ... "); + d.thenText(" ... "); } } else 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) { - d.color(ChatColor.WHITE); + pDisp.color(Pandacube.CHAT_COMMAND_HIGHLIGHTED_COLOR); } + d.then(pDisp); 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, boolean console) { - int textWidth = strWidth(text.toPlainText(), console, false); - if (textWidth > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH)) return text; + int textWidth = componentWidth(text, console); + int maxWidth = (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH; + + if (textWidth > maxWidth) + return text; + + int repeatedCharWidth = charW(repeatedChar, console, false); + int sideWidth = (maxWidth - textWidth) / 2; + int sideNbChar = sideWidth / repeatedCharWidth; + + if (sideNbChar == 0) + return text; + + String sideChars = repeatedChar(repeatedChar, sideNbChar); - String current = text.toPlainText(); - int count = 0; - do { - count++; - current = repeatedChar + current + repeatedChar; - } while (strWidth(current, console, false) <= ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH)); - count--; - - String finalLeftOrRight = ""; - - for (int i = 0; i < count; i++) - finalLeftOrRight += repeatedChar; - - Display d = new Display().next(finalLeftOrRight).color(decorationColor).next(text); - - if (repeatedChar != ' ') d.next(finalLeftOrRight).color(decorationColor); + Chat d = Chat.chat() + .then(text(sideChars).color(decorationColor)) + .then(text); + if (repeatedChar != ' ') + d.then(text(sideChars).color(decorationColor)); return d.get(); @@ -209,91 +213,60 @@ public class DisplayUtil { public static BaseComponent leftText(BaseComponent text, char repeatedChar, ChatColor decorationColor, int nbLeft, boolean console) { - - int textWidth = strWidth(text.toPlainText(), console, false); - if (textWidth > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH) || textWidth - + nbLeft * charW(repeatedChar, console, false) > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH)) + + int textWidth = componentWidth(text, console); + int maxWidth = (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH; + int repeatedCharWidth = charW(repeatedChar, console, false); + int leftWidth = nbLeft * repeatedCharWidth; + + if (textWidth + leftWidth > maxWidth) return text; - Display d = new Display(); - - String finalLeft = ""; - if (nbLeft > 0) { - for (int i = 0; i < nbLeft; i++) - finalLeft += repeatedChar; - d.next(finalLeft).color(decorationColor); - } - d.next(text); - - int count = 0; - String current = finalLeft + text.toPlainText(); - do { - count++; - current += repeatedChar; - } while (strWidth(current, console, false) <= ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH)); - count--; - + int rightNbChar = (maxWidth - (textWidth + leftWidth)) / repeatedCharWidth; + + Chat d = chat() + .then(text(repeatedChar(repeatedChar, nbLeft)).color(decorationColor)) + .then(text); if (repeatedChar != ' ') { - String finalRight = ""; - for (int i = 0; i < count; i++) - finalRight += repeatedChar; - d.next(finalRight).color(decorationColor); + d.then(text(repeatedChar(repeatedChar, rightNbChar)).color(decorationColor)); } - return d.get(); - + } public static BaseComponent rightText(BaseComponent text, char repeatedChar, ChatColor decorationColor, int nbRight, boolean console) { - - int textWidth = strWidth(text.toPlainText(), console, false); - if (textWidth > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH) || textWidth - + nbRight * charW(repeatedChar, console, false) > ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH)) + + int textWidth = componentWidth(text, console); + int maxWidth = (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH; + int repeatedCharWidth = charW(repeatedChar, console, false); + int rightWidth = nbRight * repeatedCharWidth; + + if (textWidth + rightWidth > maxWidth) return text; - String tempText = text.toPlainText(); - if (nbRight > 0) { - tempText += decorationColor; - for (int i = 0; i < nbRight; i++) - tempText += repeatedChar; - } - - int count = 0; - String current = tempText; - do { - count++; - current = repeatedChar + current; - } while (strWidth(current, console, false) <= ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH)); - count--; - - String finalLeft = ""; - for (int i = 0; i < count; i++) - finalLeft += repeatedChar; - - Display d = new Display().next(finalLeft).color(decorationColor).next(text); - + int leftNbChar = (maxWidth - (textWidth + rightWidth)) / repeatedCharWidth; + + Chat d = chat() + .then(text(repeatedChar(repeatedChar, leftNbChar)).color(decorationColor)) + .then(text); if (repeatedChar != ' ') { - String finalRight = ""; - for (int i = 0; i < nbRight; i++) - finalRight += repeatedChar; - d.next(finalRight).color(decorationColor); + d.then(text(repeatedChar(repeatedChar, nbRight)).color(decorationColor)); } - return d.get(); } public static BaseComponent emptyLine(char repeatedChar, ChatColor decorationColor, boolean console) { int count = ((console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH) / charW(repeatedChar, console, false); - String finalLine = ""; - for (int i = 0; i < count; i++) - finalLine += repeatedChar; - - return new Display().next(finalLine).color(decorationColor).get(); + return text(repeatedChar(repeatedChar, count)).color(decorationColor).get(); } - + private static String repeatedChar(char repeatedChar, int count) { + char[] c = new char[count]; + Arrays.fill(c, repeatedChar); + return new String(c); + } @@ -304,13 +277,23 @@ public class DisplayUtil { 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) { + if (component == null) + return 0; + int count = 0; - for (BaseComponent c : component.getExtra()) - count += componentWidth(c, console); + if (component instanceof TextComponent) { count += strWidth(((TextComponent)component).getText(), console, component.isBold()); } @@ -318,6 +301,11 @@ public class DisplayUtil { for (BaseComponent c : ((TranslatableComponent)component).getWith()) count += componentWidth(c, console); } + + if (component.getExtra() != null) { + for (BaseComponent c : component.getExtra()) + count += componentWidth(c, console); + } return count; } @@ -368,14 +356,15 @@ public class DisplayUtil { if ((c >= '0' && c <= '9') // reset bold || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') - || c == 'r' || c == 'R') + || c == 'r' || c == 'R' + || c == 'x' || c == 'X') bold = false; } else if (c == ' ') { if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word lines.add(currentLine); - String lastStyle = getLastColors(currentLine); + String lastStyle = ChatColorUtil.getLastColors(currentLine); if (currentWord.charAt(0) == ' ') { currentWord = currentWord.substring(1); currentWordSize -= charW(' ', false, firstCharCurrentWorldBold); @@ -394,7 +383,7 @@ public class DisplayUtil { else if (c == '\n') { if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word lines.add(currentLine); - String lastStyle = getLastColors(currentLine); + String lastStyle = ChatColorUtil.getLastColors(currentLine); if (currentWord.charAt(0) == ' ') { currentWord = currentWord.substring(1); currentWordSize -= charW(' ', false, firstCharCurrentWorldBold); @@ -408,7 +397,7 @@ public class DisplayUtil { } // wrap after lines.add(currentLine); - String lastStyle = getLastColors(currentLine); + String lastStyle = ChatColorUtil.getLastColors(currentLine); currentLine = lastStyle.equals("§r") ? "" : lastStyle; 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. - * 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); + + public static BaseComponent toUniqueBaseComponent(BaseComponent... baseComponents) { + if (baseComponents == null || baseComponents.length == 0) + return new TextComponent(); + if (baseComponents.length == 1) + return baseComponents[0]; + return new TextComponent(baseComponents); } - /** - * 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); - } - - - diff --git a/src/main/java/fr/pandacube/util/text_display/Display.java b/src/main/java/fr/pandacube/util/text_display/Display.java deleted file mode 100644 index 54f2153..0000000 --- a/src/main/java/fr/pandacube/util/text_display/Display.java +++ /dev/null @@ -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 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()]); - } - -} diff --git a/src/main/java/fr/pandacube/util/text_display/TextProgressBar.java b/src/main/java/fr/pandacube/util/text_display/TextProgressBar.java index 1288755..c5e3b9c 100644 --- a/src/main/java/fr/pandacube/util/text_display/TextProgressBar.java +++ b/src/main/java/fr/pandacube/util/text_display/TextProgressBar.java @@ -6,7 +6,6 @@ public class TextProgressBar { private static String pattern_start = "["; private static String pattern_end = "]"; private static ChatColor color_empty = ChatColor.DARK_GRAY; - private static ChatColor color_decoration = ChatColor.GOLD; private static ChatColor color_default = ChatColor.RESET; private static String pattern_empty = "."; private static String pattern_full = "|"; @@ -35,7 +34,7 @@ public class TextProgressBar { } int sum_sizes = 0; - String bar = color_decoration + pattern_start; + String bar = pattern_start; for (int i = 0; i < sizes.length; i++) { sum_sizes += sizes[i]; @@ -52,7 +51,7 @@ public class TextProgressBar { for (int j = 0; j < (max_size - sum_sizes); j++) bar = bar + pattern_empty; - bar = bar + color_decoration + pattern_end; + bar = bar + ChatColor.RESET + pattern_end; return bar; }