Progress javadoc, various refactor + new module pandalib-commands
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
package fr.pandacube.lib.core.commands;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import fr.pandacube.lib.chat.ChatStatic;
|
||||
|
||||
public class AbstractCommand extends ChatStatic {
|
||||
|
||||
public final String commandName;
|
||||
|
||||
public AbstractCommand(String cmdName) {
|
||||
commandName = cmdName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Concatène les chaines de caractères passés dans <code>args</code> (avec
|
||||
* <code>" "</code> comme séparateur), en ommettant
|
||||
* celles qui se trouvent avant <code>index</code>.<br/>
|
||||
* Par exemple :
|
||||
* </p>
|
||||
* <code>
|
||||
* getLastParams(new String[] {"test", "bouya", "chaka", "bukkit"}, 1);
|
||||
* </code>
|
||||
* <p>
|
||||
* retournera la chaine "bouya chaka bukkit"
|
||||
*
|
||||
* @param args liste des arguments d'une commandes.<br/>
|
||||
* Le premier élément est l'argument qui suit le nom de la commande.
|
||||
* Usuellement, ce paramètre correspond au paramètre
|
||||
* <code>args</code> de la méthode onCommand
|
||||
*/
|
||||
public static String getLastParams(String[] args, int index) {
|
||||
return String.join(" ", Arrays.copyOfRange(args, index, args.length));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
package fr.pandacube.lib.core.commands;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Throw an instance of this exception to indicate to the plugin command handler
|
||||
* that the user has missused the command. The message, if provided, must indicate
|
||||
* the reason of the mussusage of the command. It will be displayed on the screen
|
||||
* with eventually indication of how to use the command (help command for example).
|
||||
* If a {@link Throwable} cause is provided, it will be relayed to the plugin {@link Logger}.
|
||||
*
|
||||
*/
|
||||
public class BadCommandUsage extends RuntimeException {
|
||||
|
||||
public BadCommandUsage() {
|
||||
super();
|
||||
}
|
||||
|
||||
public BadCommandUsage(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public BadCommandUsage(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BadCommandUsage(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@@ -1,387 +0,0 @@
|
||||
package fr.pandacube.lib.core.commands;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.LongStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import fr.pandacube.lib.util.ListUtil;
|
||||
import fr.pandacube.lib.util.TimeUtil;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SuggestionsSupplier<S> {
|
||||
|
||||
/**
|
||||
* Number of suggestion visible at once without having to scroll
|
||||
*/
|
||||
int VISIBLE_SUGGESTION_COUNT = 10;
|
||||
|
||||
|
||||
List<String> getSuggestions(S sender, int tokenIndex, String token, String[] args);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static Predicate<String> filter(String token) {
|
||||
return suggestion -> suggestion != null && suggestion.toLowerCase().startsWith(token.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the provided {@link Stream} of string according to the provided token, using the filter returned by {@link #filter(String)},
|
||||
* then returns the strings collected into a {@link List}.
|
||||
*
|
||||
* This methods consume the provided stream, so will not be usable anymore.
|
||||
*/
|
||||
static List<String> collectFilteredStream(Stream<String> stream, String token) {
|
||||
return stream.filter(filter(token)).sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static <S> SuggestionsSupplier<S> empty() { return (s, ti, t, a) -> Collections.emptyList(); }
|
||||
|
||||
static <S> SuggestionsSupplier<S> fromCollectionsSupplier(Supplier<Collection<String>> streamSupplier) {
|
||||
return (s, ti, token, a) -> collectFilteredStream(streamSupplier.get().stream(), token);
|
||||
}
|
||||
|
||||
static <S> SuggestionsSupplier<S> fromStreamSupplier(Supplier<Stream<String>> streamSupplier) {
|
||||
return (s, ti, token, a) -> collectFilteredStream(streamSupplier.get(), token);
|
||||
}
|
||||
|
||||
static <S> SuggestionsSupplier<S> fromCollection(Collection<String> suggestions) {
|
||||
return fromStreamSupplier(suggestions::stream);
|
||||
}
|
||||
|
||||
static <S> SuggestionsSupplier<S> fromArray(String... suggestions) {
|
||||
return fromStreamSupplier(() -> Arrays.stream(suggestions));
|
||||
}
|
||||
|
||||
|
||||
static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnum(Class<E> enumClass) {
|
||||
return fromEnumValues(enumClass.getEnumConstants());
|
||||
}
|
||||
|
||||
static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnum(Class<E> enumClass, boolean lowerCase) {
|
||||
return fromEnumValues(lowerCase, enumClass.getEnumConstants());
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnumValues(E... enumValues) {
|
||||
return fromEnumValues(false, enumValues);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnumValues(boolean lowerCase, E... enumValues) {
|
||||
return (s, ti, token, a) -> {
|
||||
Stream<String> st = Arrays.stream(enumValues).map(Enum::name);
|
||||
if (lowerCase)
|
||||
st = st.map(String::toLowerCase);
|
||||
return collectFilteredStream(st, token);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
static <S> SuggestionsSupplier<S> booleanValues() {
|
||||
return fromCollection(Arrays.asList("true", "false"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link SuggestionsSupplier} that suggest numbers according to the provided range.
|
||||
*
|
||||
* The current implementation only support range that include either -1 or 1.
|
||||
*/
|
||||
static <S> SuggestionsSupplier<S> fromIntRange(int min, int max, boolean compact) {
|
||||
return fromLongRange(min, max, compact);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link SuggestionsSupplier} that suggest numbers according to the provided range.
|
||||
*
|
||||
* The current implementation only support range that include either -1 or 1.
|
||||
*/
|
||||
static <S> SuggestionsSupplier<S> fromLongRange(long min, long max, boolean compact) {
|
||||
if (max < min) {
|
||||
throw new IllegalArgumentException("min should be less or equals than max");
|
||||
}
|
||||
if (compact) {
|
||||
return (s, ti, token, a) -> {
|
||||
try {
|
||||
List<Long> proposedValues = new ArrayList<>();
|
||||
if (token.length() == 0) {
|
||||
long start = Math.max(Math.max(Math.min(-4, max - 9), min), -9);
|
||||
long end = Math.min(Math.min(start + 9, max), 9);
|
||||
ListUtil.addLongRangeToList(proposedValues, start, end);
|
||||
}
|
||||
else if (token.length() == 1) {
|
||||
if (token.charAt(0) == '0') {
|
||||
if (min > 0 || max < 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
else
|
||||
return Collections.singletonList(token);
|
||||
}
|
||||
else if (token.charAt(0) == '-') {
|
||||
ListUtil.addLongRangeToList(proposedValues, Math.max(-9, min), -1);
|
||||
}
|
||||
else {
|
||||
long lToken = Long.parseLong(token);
|
||||
if (lToken > max) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
lToken *= 10;
|
||||
if (lToken > max) {
|
||||
return Collections.singletonList(token);
|
||||
}
|
||||
|
||||
ListUtil.addLongRangeToList(proposedValues, lToken, Math.min(lToken + 9, max));
|
||||
}
|
||||
}
|
||||
else {
|
||||
long lToken = Long.parseLong(token);
|
||||
if (lToken < min || lToken > max) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
lToken *= 10;
|
||||
if (lToken < min || lToken > max) {
|
||||
return Collections.singletonList(token);
|
||||
}
|
||||
|
||||
if (lToken < 0) {
|
||||
ListUtil.addLongRangeToList(proposedValues, Math.max(lToken - 9, min), lToken);
|
||||
}
|
||||
else {
|
||||
ListUtil.addLongRangeToList(proposedValues, lToken, Math.min(lToken + 9, max));
|
||||
}
|
||||
}
|
||||
|
||||
return collectFilteredStream(proposedValues.stream().map(Object::toString), token);
|
||||
} catch (NumberFormatException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
return (s, ti, token, a) -> collectFilteredStream(LongStream.rangeClosed(min, max).mapToObj(Long::toString), token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static <S> SuggestionsSupplier<S> suggestDuration() {
|
||||
final List<String> emptyTokenSuggestions = DURATION_SUFFIXES.stream().map(p -> "1" + p).collect(Collectors.toList());
|
||||
return (s, ti, token, args) -> {
|
||||
if (token.isEmpty()) {
|
||||
return emptyTokenSuggestions;
|
||||
}
|
||||
List<String> remainingSuffixes = new ArrayList<>(DURATION_SUFFIXES);
|
||||
char[] tokenChars = token.toCharArray();
|
||||
String accSuffix = "";
|
||||
for (char c : tokenChars) {
|
||||
if (Character.isDigit(c)) {
|
||||
scanAndRemovePastSuffixes(remainingSuffixes, accSuffix);
|
||||
accSuffix = "";
|
||||
} 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());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all possible duration unit symbols for suggestions.
|
||||
*/
|
||||
public static final List<String> DURATION_SUFFIXES = List.of("y", "mo", "w", "d", "h", "m", "s");
|
||||
|
||||
|
||||
private static void scanAndRemovePastSuffixes(List<String> suffixes, String foundSuffix) {
|
||||
for (int i = 0; i < suffixes.size(); i++) {
|
||||
if (foundSuffix.startsWith(suffixes.get(i))) {
|
||||
suffixes.subList(0, i + 1).clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link SuggestionsSupplier} that support greedy strings argument using the suggestion from this {@link SuggestionsSupplier}.
|
||||
* @param index the index of the first argument of the greedy string argument
|
||||
*/
|
||||
default SuggestionsSupplier<S> greedyString(int index) {
|
||||
|
||||
return (s, ti, token, args) -> {
|
||||
|
||||
if (ti < index)
|
||||
return Collections.emptyList();
|
||||
|
||||
String gToken = AbstractCommand.getLastParams(args, index);
|
||||
String[] splitGToken = gToken.split(" ", -1);
|
||||
int currentTokenPosition = splitGToken.length - 1;
|
||||
String[] prevWordsGToken = Arrays.copyOf(splitGToken, currentTokenPosition);
|
||||
|
||||
String[] argsWithMergedGreedyToken = Arrays.copyOf(args, index + 1);
|
||||
argsWithMergedGreedyToken[index] = gToken;
|
||||
|
||||
List<String> currentTokenProposal = new ArrayList<>();
|
||||
for (String suggestion : getSuggestions(s, index, gToken, argsWithMergedGreedyToken)) {
|
||||
String[] splitSuggestion = suggestion.split(" ", -1);
|
||||
if (splitSuggestion.length <= currentTokenPosition)
|
||||
continue;
|
||||
if (!Arrays.equals(Arrays.copyOf(splitGToken, currentTokenPosition), prevWordsGToken))
|
||||
continue;
|
||||
if (splitSuggestion[currentTokenPosition].isEmpty())
|
||||
continue;
|
||||
|
||||
currentTokenProposal.add(splitSuggestion[currentTokenPosition]);
|
||||
}
|
||||
return currentTokenProposal;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
default SuggestionsSupplier<S> quotableString() {
|
||||
return (s, ti, token, a) -> {
|
||||
boolean startWithQuote = token.length() > 0 && (token.charAt(0) == '"' || token.charAt(0) == '\'');
|
||||
String realToken = startWithQuote ? unescapeBrigadierQuotable(token.substring(1), token.charAt(0)) : token;
|
||||
String[] argsCopy = Arrays.copyOf(a, a.length);
|
||||
argsCopy[a.length - 1] = realToken;
|
||||
List<String> rawResults = getSuggestions(s, ti, realToken, argsCopy);
|
||||
|
||||
boolean needsQuotes = false;
|
||||
for (String res : rawResults) {
|
||||
if (!isAllowedInBrigadierUnquotedString(res)) {
|
||||
needsQuotes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return needsQuotes
|
||||
? rawResults.stream().map(SuggestionsSupplier::escapeBrigadierQuotable).collect(Collectors.toList())
|
||||
: rawResults;
|
||||
};
|
||||
}
|
||||
|
||||
// inspired from com.mojang.brigadier.StringReader#readQuotedString()
|
||||
static String unescapeBrigadierQuotable(String input, char quote) {
|
||||
StringBuilder builder = new StringBuilder(input.length());
|
||||
boolean escaped = false;
|
||||
for (char c : input.toCharArray()) {
|
||||
if (escaped) {
|
||||
if (c == quote || c == '\\') {
|
||||
escaped = false;
|
||||
} else {
|
||||
builder.append('\\');
|
||||
}
|
||||
builder.append(c);
|
||||
} else if (c == '\\') {
|
||||
escaped = true;
|
||||
} else if (c == quote) {
|
||||
return builder.toString();
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
// from com.mojang.brigadier.StringReader#isAllowedInUnquotedString(char)
|
||||
static boolean isAllowedInBrigadierUnquotedString(char c) {
|
||||
return c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'
|
||||
|| c == '_' || c == '-' || c == '.' || c == '+';
|
||||
}
|
||||
static boolean isAllowedInBrigadierUnquotedString(String s) {
|
||||
for (char c : s.toCharArray())
|
||||
if (!isAllowedInBrigadierUnquotedString(c))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static String escapeBrigadierQuotable(final String input) {
|
||||
final StringBuilder result = new StringBuilder("\"");
|
||||
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
final char c = input.charAt(i);
|
||||
if (c == '\\' || c == '"') {
|
||||
result.append('\\');
|
||||
}
|
||||
result.append(c);
|
||||
}
|
||||
|
||||
result.append("\"");
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
default SuggestionsSupplier<S> requires(Predicate<S> check) {
|
||||
return (s, ti, to, a) -> 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()}.
|
||||
*/
|
||||
default SuggestionsSupplier<S> merge(SuggestionsSupplier<S> other) {
|
||||
return (s, ti, to, a) -> {
|
||||
List<String> l1 = getSuggestions(s, ti, to, a);
|
||||
List<String> l2 = other.getSuggestions(s, ti, to, a);
|
||||
return Stream.concat(l1.stream(), l2.stream())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new {@link SuggestionsSupplier} containing all the suggestions of this instance,
|
||||
* but if this list is still empty, returns the suggestions from the provided one.
|
||||
*/
|
||||
default SuggestionsSupplier<S> orIfEmpty(SuggestionsSupplier<S> other) {
|
||||
return (s, ti, to, a) -> {
|
||||
List<String> l1 = getSuggestions(s, ti, to, a);
|
||||
return !l1.isEmpty() ? l1 : other.getSuggestions(s, ti, to, a);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -12,24 +12,23 @@ import fr.pandacube.lib.chat.ChatColorUtil;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
|
||||
/**
|
||||
* Class tht loads a specific config file or directory
|
||||
*
|
||||
* Class that loads a specific config file or directory.
|
||||
*/
|
||||
public abstract class AbstractConfig {
|
||||
|
||||
/**
|
||||
* Correspond au dossier ou au fichier de configuration traité par la sous-classe
|
||||
* courante de {@link AbstractConfig}
|
||||
* The {@link File} corresponging to this config file or directory.
|
||||
*/
|
||||
protected final File configFile;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractConfig}.
|
||||
* @param configDir the parent directory
|
||||
* @param fileOrDirName The name of the config file or folder
|
||||
* @param type if the provided name is a file or a directory
|
||||
* @throws IOException if we cannot create the file
|
||||
* @throws IOException if we cannot create the file or directory.
|
||||
*/
|
||||
public AbstractConfig(File configDir, String fileOrDirName, FileType type) throws IOException {
|
||||
protected AbstractConfig(File configDir, String fileOrDirName, FileType type) throws IOException {
|
||||
configFile = new File(configDir, fileOrDirName);
|
||||
if (type == FileType.DIR)
|
||||
configFile.mkdir();
|
||||
@@ -38,14 +37,15 @@ public abstract class AbstractConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the lines from the config file
|
||||
* @param ignoreEmpty <code>true</code> if we ignore the empty lines
|
||||
* @param ignoreHashtagComment <code>true</code> if we ignore the comment lines (starting with {@code #})
|
||||
* @param trimOutput <code>true</code> if we want to trim all lines using {@link String#trim()}
|
||||
* @param f the file to read
|
||||
* @return the list of lines, filtered according to the parameters
|
||||
* Gets the lines from the provided file.
|
||||
* @param ignoreEmpty {@code true} if we ignore the empty lines.
|
||||
* @param ignoreHashtagComment {@code true} if we ignore the comment lines (starting with {@code #}).
|
||||
* @param trimOutput {@code true} if we want to trim all lines using {@link String#trim()}.
|
||||
* @param f the file to read.
|
||||
* @return the list of lines, filtered according to the parameters, or null if it’s not a regular file.
|
||||
* @throws IOException if an IO error occurs.
|
||||
*/
|
||||
protected List<String> getFileLines(boolean ignoreEmpty, boolean ignoreHashtagComment, boolean trimOutput, File f) throws IOException {
|
||||
protected static List<String> getFileLines(boolean ignoreEmpty, boolean ignoreHashtagComment, boolean trimOutput, File f) throws IOException {
|
||||
if (!f.isFile())
|
||||
return null;
|
||||
|
||||
@@ -74,26 +74,27 @@ public abstract class AbstractConfig {
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Retourne toutes les lignes du fichier de configuration
|
||||
* @param ignoreEmpty <code>true</code> si on doit ignorer les lignes vides
|
||||
* @param ignoreHashtagComment <code>true</code> si on doit ignorer les lignes commentés (commençant par un #)
|
||||
* @param trimOutput <code>true</code> si on doit appeller la méthode String.trim() sur chaque ligne retournée
|
||||
* @return la liste des lignes utiles
|
||||
* Gets the lines from the config file.
|
||||
* @param ignoreEmpty {@code true} if we ignore the empty lines.
|
||||
* @param ignoreHashtagComment {@code true} if we ignore the comment lines (starting with {@code #}).
|
||||
* @param trimOutput {@code true} if we want to trim all lines using {@link String#trim()}.
|
||||
* @return the list of lines, filtered according to the parameters, or null if it’s not a regular file.
|
||||
* @throws IOException if an IO error occurs.
|
||||
*/
|
||||
protected List<String> getFileLines(boolean ignoreEmpty, boolean ignoreHashtagComment, boolean trimOutput) throws IOException {
|
||||
return getFileLines(ignoreEmpty, ignoreHashtagComment, trimOutput, configFile);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the list of files in the config directory.
|
||||
* @return the list of files in the config directory, or null if this config is not a directory.
|
||||
*/
|
||||
protected List<File> getFileList() {
|
||||
if (!configFile.isDirectory())
|
||||
return null;
|
||||
|
||||
return Arrays.asList(configFile.listFiles());
|
||||
return configFile.isDirectory() ? Arrays.asList(configFile.listFiles()) : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,43 +102,45 @@ public abstract class AbstractConfig {
|
||||
|
||||
|
||||
/**
|
||||
* Découpe une chaine de caractère contenant une série de noeuds
|
||||
* de permissions séparés par des point-virgules et la retourne sous forme d'une liste.
|
||||
* @param perms la chaine de permissions à traiter
|
||||
* @return <code>null</code> si le paramètre est nulle ou si <code>perms.equals("*")</code>, ou alors la chaine splittée.
|
||||
* Splits the provided string into a list of permission nodes.
|
||||
* The permission nodes must be separated by {@code ";"}.
|
||||
* @param perms one or more permissions nodes, separated by {@code ";"}.
|
||||
* @return {@code null} if the parameter is null or is equal to {@code "*"}, or the string splitted using {@code ";"}.
|
||||
*/
|
||||
public static List<String> splitPermissionsString(String perms) {
|
||||
if (perms == null || perms.equals("*"))
|
||||
return null;
|
||||
return getSplittedString(perms, ";");
|
||||
return List.of(perms.split(";"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static List<String> getSplittedString(String value, String split) {
|
||||
return List.of(value.split(split));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Utility method to that translate the {@code '&'} formated string to the legacy format.
|
||||
* @param string the string to convert.
|
||||
* @return a legacy formated string (using {@code '§'}).
|
||||
*/
|
||||
public static String getTranslatedColorCode(String string) {
|
||||
return ChatColorUtil.translateAlternateColorCodes('&', string);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Logs the message as a warning into console, prefixed with the context of this config.
|
||||
* @param message the message to log.
|
||||
*/
|
||||
protected void warning(String message) {
|
||||
Log.warning("Erreur dans la configuration de '"+configFile.getName()+"' : "+message);
|
||||
Log.warning("Error in configuration '"+configFile.getName()+"': " + message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The type of config.
|
||||
*/
|
||||
protected enum FileType {
|
||||
FILE, DIR
|
||||
/** A config file. */
|
||||
FILE,
|
||||
/** A config directory. */
|
||||
DIR
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,10 +3,23 @@ package fr.pandacube.lib.core.config;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An abstract manager for a set of configuration files and folders.
|
||||
* Its uses is to manage the loading/reloading of the configuration of a plugin.
|
||||
*/
|
||||
public abstract class AbstractConfigManager {
|
||||
|
||||
|
||||
/**
|
||||
* The global configuration directory.
|
||||
* May be the one provided by the environmenet API (like Plugin.getPluginFolder() in Bukkit).
|
||||
*/
|
||||
protected final File configDir;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance of config manager.
|
||||
* @param configD the config directory.
|
||||
* @throws IOException if an IO error occurs.
|
||||
*/
|
||||
public AbstractConfigManager(File configD) throws IOException {
|
||||
configDir = configD;
|
||||
|
||||
@@ -16,18 +29,24 @@ public abstract class AbstractConfigManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation must close all closeable configuration (saving for example)
|
||||
* Closes the configuration. May handle saving of any non-saved data.
|
||||
* @throws IOException if an IO error occurs.
|
||||
*/
|
||||
public abstract void close() throws IOException;
|
||||
|
||||
/**
|
||||
* Implementation must init all config data
|
||||
* Loads (or reloads) the configuration data.
|
||||
* @throws IOException if an IO error occurs.
|
||||
*/
|
||||
public abstract void init() throws IOException;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Utility method to close then reload the config.
|
||||
* @throws IOException if an IO error occurs.
|
||||
* @see #close()
|
||||
* @see #init()
|
||||
*/
|
||||
public synchronized void reloadConfig() throws IOException {
|
||||
close();
|
||||
init();
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package fr.pandacube.lib.core.util;
|
||||
package fr.pandacube.lib.core.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
@@ -17,13 +17,40 @@ import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* Provides pre-instanciated {@link Gson} instances, all with support for Java records.
|
||||
*/
|
||||
public class Json {
|
||||
|
||||
/**
|
||||
* {@link Gson} instance with {@link GsonBuilder#setLenient()} and support for Java records.
|
||||
*/
|
||||
public static final Gson gson = build(Function.identity());
|
||||
|
||||
/**
|
||||
* {@link Gson} instance with {@link GsonBuilder#setLenient()}, {@link GsonBuilder#setPrettyPrinting()}
|
||||
* and support for Java records.
|
||||
*/
|
||||
public static final Gson gsonPrettyPrinting = build(GsonBuilder::setPrettyPrinting);
|
||||
|
||||
/**
|
||||
* {@link Gson} instance with {@link GsonBuilder#setLenient()}, {@link GsonBuilder#serializeNulls()}
|
||||
* and support for Java records.
|
||||
*/
|
||||
public static final Gson gsonSerializeNulls = build(GsonBuilder::serializeNulls);
|
||||
|
||||
/**
|
||||
* {@link Gson} instance with {@link GsonBuilder#setLenient()}, {@link GsonBuilder#serializeNulls()},
|
||||
* {@link GsonBuilder#setPrettyPrinting()} and support for Java records.
|
||||
*/
|
||||
public static final Gson gsonSerializeNullsPrettyPrinting = build(b -> b.serializeNulls().setPrettyPrinting());
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static Gson build(Function<GsonBuilder, GsonBuilder> builderModifier) {
|
||||
return builderModifier
|
||||
.apply(new GsonBuilder().registerTypeAdapterFactory(new RecordAdapterFactory()).setLenient()).create();
|
||||
@@ -32,6 +59,10 @@ public class Json {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// from https://github.com/google/gson/issues/1794#issuecomment-812964421
|
||||
private static class RecordAdapterFactory implements TypeAdapterFactory {
|
||||
@Override
|
@@ -1,4 +1,4 @@
|
||||
package fr.pandacube.lib.core.util;
|
||||
package fr.pandacube.lib.core.json;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -8,13 +8,16 @@ import java.util.Map;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Utility for conversion of basic Java types and JsonElements types
|
||||
* @author Marc
|
||||
*
|
||||
* Provides conversion between Java types and {@link JsonElement} types.
|
||||
*/
|
||||
public class TypeConverter {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Converts the provided object to an {@link Integer}.
|
||||
* @param o the object to convert.
|
||||
* @return a the object converted to an {@link Integer}.
|
||||
* @throws ConvertionException is a conversion error occurs.
|
||||
*/
|
||||
public static Integer toInteger(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
@@ -44,14 +47,26 @@ public class TypeConverter {
|
||||
|
||||
throw new ConvertionException("No integer convertion available for an instance of "+o.getClass());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the provided object to a primitive int.
|
||||
* @param o the object to convert.
|
||||
* @return a the object converted to a primitive int.
|
||||
* @throws ConvertionException is a conversion error occurs.
|
||||
*/
|
||||
public static int toPrimInt(Object o) {
|
||||
Integer val = toInteger(o);
|
||||
if (val == null)
|
||||
throw new ConvertionException("null values can't be converted to primitive int");
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the provided object to a {@link Double}.
|
||||
* @param o the object to convert.
|
||||
* @return a the object converted to a {@link Double}.
|
||||
* @throws ConvertionException is a conversion error occurs.
|
||||
*/
|
||||
public static Double toDouble(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
@@ -82,14 +97,26 @@ public class TypeConverter {
|
||||
throw new ConvertionException("No double convertion available for an instance of "+o.getClass());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the provided object to a primitive double.
|
||||
* @param o the object to convert.
|
||||
* @return a the object converted to a primitive double.
|
||||
* @throws ConvertionException is a conversion error occurs.
|
||||
*/
|
||||
public static double toPrimDouble(Object o) {
|
||||
Double val = toDouble(o);
|
||||
if (val == null)
|
||||
throw new ConvertionException("null values can't converted to primitive int");
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the provided object to a {@link String}.
|
||||
* @param o the object to convert.
|
||||
* @return a the object converted to a {@link String}.
|
||||
* @throws ConvertionException is a conversion error occurs.
|
||||
*/
|
||||
public static String toString(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
@@ -112,11 +139,13 @@ public class TypeConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param o the object to convert to good type
|
||||
* Converts the provided object to a {@link Map}.
|
||||
* @param o the object to convert.
|
||||
* @param mapIntKeys if the String key representing an int should be duplicated as integer type,
|
||||
* which map to the same value as the original String key. For example, if a key is "12" and map
|
||||
* to the object <i>o</i>, an integer key 12 will be added and map to the same object <i>o</i>
|
||||
* to the object <i>o</i>, an integer key 12 will be added and map to the same object <i>o</i>.
|
||||
* @return a the object converted to a {@link Map}.
|
||||
* @throws ConvertionException is a conversion error occurs.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<Object, Object> toMap(Object o, boolean mapIntKeys) {
|
||||
@@ -156,14 +185,17 @@ public class TypeConverter {
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
throw new ConvertionException("No Map convertion available for an instance of "+o.getClass());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Converts the provided object to a {@link List}.
|
||||
* @param o the object to convert.
|
||||
* @return a the object converted to a {@link List}.
|
||||
* @throws ConvertionException is a conversion error occurs.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<Object> toList(Object o) {
|
||||
if (o == null) {
|
||||
@@ -190,23 +222,19 @@ public class TypeConverter {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Thrown when a convertion error occurs.
|
||||
*/
|
||||
public static class ConvertionException extends RuntimeException {
|
||||
|
||||
public ConvertionException(String m) {
|
||||
private ConvertionException(String m) {
|
||||
super(m);
|
||||
}
|
||||
public ConvertionException(Throwable t) {
|
||||
private ConvertionException(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
public ConvertionException(String m, Throwable t) {
|
||||
super(m, t);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -15,9 +15,11 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Utility class to manage searching among a set of
|
||||
* SearchResult instances, using case insensitive
|
||||
* keywords.
|
||||
* Utility class to manage searching among a set of {@link SearchResult} instances, using case insensitive keywords.
|
||||
* The search engine is responsible for storing a database of entries ({@link SearchResult}) that can be searched using
|
||||
* keywords. This class provides methods to returns a list of results for provided keywords, a list of keyword
|
||||
* suggestions based on pre-typed keywords.
|
||||
* @param <R> the type of search result.
|
||||
*/
|
||||
public class SearchEngine<R extends SearchResult> {
|
||||
|
||||
@@ -30,13 +32,21 @@ public class SearchEngine<R extends SearchResult> {
|
||||
private final Set<R> resultSet = new HashSet<>();
|
||||
|
||||
private final Cache<Set<String>, List<String>> suggestionsCache;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new search engine.
|
||||
* @param suggestionsCacheSize the size of the cache for keyword suggestions (for optimization).
|
||||
*/
|
||||
public SearchEngine(int suggestionsCacheSize) {
|
||||
suggestionsCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(suggestionsCacheSize)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an entry in this search engine.
|
||||
* @param result the new entry.
|
||||
*/
|
||||
public synchronized void addResult(R result) {
|
||||
if (result == null)
|
||||
throw new IllegalArgumentException("Provided result cannot be null.");
|
||||
@@ -83,7 +93,11 @@ public class SearchEngine<R extends SearchResult> {
|
||||
|
||||
suggestionsCache.invalidateAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the provided entry from this search engine.
|
||||
* @param result the new entry.
|
||||
*/
|
||||
public synchronized void removeResult(R result) {
|
||||
if (result == null || !resultSet.contains(result))
|
||||
return;
|
||||
@@ -118,7 +132,12 @@ public class SearchEngine<R extends SearchResult> {
|
||||
|
||||
suggestionsCache.invalidateAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provides all the search results that correspond to all the provided keywords.
|
||||
* @param searchTerms all the search terms (keywords).
|
||||
* @return all the search results that correspond to all the provided keywords.
|
||||
*/
|
||||
public synchronized Set<R> search(Set<String> searchTerms) {
|
||||
if (searchTerms == null)
|
||||
searchTerms = new HashSet<>();
|
||||
@@ -130,7 +149,12 @@ public class SearchEngine<R extends SearchResult> {
|
||||
|
||||
return retainedResults;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provides all the search results that correspond to the provided keyword.
|
||||
* @param searchTerm the search term (keyword). If null, all the possible results are returned.
|
||||
* @return all the search results that correspond to the provided keyword.
|
||||
*/
|
||||
public synchronized Set<R> search(String searchTerm) {
|
||||
if (searchTerm == null || searchTerm.isEmpty()) {
|
||||
return new HashSet<>(resultSet);
|
||||
@@ -145,7 +169,12 @@ public class SearchEngine<R extends SearchResult> {
|
||||
|
||||
return retainedResults;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provides the next keyword to suggest, based on the already typed keywords.
|
||||
* @param prevSearchTerms the already typed keywords.
|
||||
* @return the next keywords to suggest.
|
||||
*/
|
||||
public synchronized List<String> suggestKeywords(List<String> prevSearchTerms) {
|
||||
if (prevSearchTerms == null || prevSearchTerms.isEmpty()) {
|
||||
return new ArrayList<>(suggestionsKeywordsResultMap.keySet());
|
||||
|
@@ -2,10 +2,22 @@ package fr.pandacube.lib.core.search;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An entry in the {@link SearchEngine}.
|
||||
*/
|
||||
public interface SearchResult {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the keywords corresponding to this search result.
|
||||
* @return the keywords corresponding to this search result.
|
||||
*/
|
||||
Set<String> getSearchKeywords();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the keywords to suggest corresponding to this search result.
|
||||
* It may be different from the search keywords.
|
||||
* @return the keywords to suggest corresponding to this search result.
|
||||
*/
|
||||
Set<String> getSuggestionKeywords();
|
||||
|
||||
}
|
||||
|
@@ -1,129 +0,0 @@
|
||||
package fr.pandacube.lib.core.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import fr.pandacube.lib.util.Log;
|
||||
|
||||
public class ServerPropertyFile {
|
||||
|
||||
private final transient File file;
|
||||
|
||||
private String name = "default_name";
|
||||
private String memory = "512M";
|
||||
private String javaArgs = "";
|
||||
private String MinecraftArgs = "";
|
||||
private String jarFile = "";
|
||||
private long startupDelay = 0;
|
||||
private boolean run = true;
|
||||
|
||||
public ServerPropertyFile(File f) {
|
||||
file = Objects.requireNonNull(f, "f");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge le fichier de configuration dans cette instance de la classe
|
||||
*
|
||||
* @return true si le chargement a réussi, false sinon
|
||||
*/
|
||||
public boolean loadFromFile() {
|
||||
try (BufferedReader in = new BufferedReader(new FileReader(file))) {
|
||||
|
||||
ServerPropertyFile dataFile = Json.gsonPrettyPrinting.fromJson(in, getClass());
|
||||
|
||||
name = dataFile.name;
|
||||
memory = dataFile.memory;
|
||||
javaArgs = dataFile.javaArgs;
|
||||
MinecraftArgs = dataFile.MinecraftArgs;
|
||||
jarFile = dataFile.jarFile;
|
||||
run = dataFile.run;
|
||||
startupDelay = dataFile.startupDelay;
|
||||
|
||||
return true;
|
||||
} catch(JsonSyntaxException e) {
|
||||
Log.severe("Error in config file " + file + ": backed up and creating a new one from previous or default values.", e);
|
||||
return save();
|
||||
} catch (IOException e) {
|
||||
Log.severe(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean save() {
|
||||
try (BufferedWriter out = new BufferedWriter(new FileWriter(file, false))) {
|
||||
Json.gsonPrettyPrinting.toJson(this, out);
|
||||
out.flush();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.severe(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getMemory() {
|
||||
return memory;
|
||||
}
|
||||
|
||||
public String getJavaArgs() {
|
||||
return javaArgs;
|
||||
}
|
||||
|
||||
public String getMinecraftArgs() {
|
||||
return MinecraftArgs;
|
||||
}
|
||||
|
||||
public String getJarFile() {
|
||||
return jarFile;
|
||||
}
|
||||
|
||||
public boolean isRun() {
|
||||
return run;
|
||||
}
|
||||
|
||||
public long getStartupDelay() {
|
||||
return startupDelay;
|
||||
}
|
||||
|
||||
public void setName(String n) {
|
||||
if (n == null || !n.matches("^[a-zA-Z]$")) throw new IllegalArgumentException();
|
||||
name = n;
|
||||
}
|
||||
|
||||
public void setMemory(String m) {
|
||||
if (m == null || !m.matches("^\\d+[mgMG]$")) throw new IllegalArgumentException();
|
||||
memory = m;
|
||||
}
|
||||
|
||||
public void setJavaArgs(String ja) {
|
||||
if (ja == null) throw new IllegalArgumentException();
|
||||
javaArgs = ja;
|
||||
}
|
||||
|
||||
public void setMinecraftArgs(String ma) {
|
||||
if (ma == null) throw new IllegalArgumentException();
|
||||
MinecraftArgs = ma;
|
||||
}
|
||||
|
||||
public void setJarFile(String j) {
|
||||
if (j == null) throw new IllegalArgumentException();
|
||||
jarFile = j;
|
||||
}
|
||||
|
||||
public void setRun(boolean r) {
|
||||
run = r;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user