2020-04-06 00:10:14 +02:00
|
|
|
package fr.pandacube.util.commands;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.function.Predicate;
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
import java.util.stream.Stream;
|
|
|
|
|
2020-05-07 18:37:06 +02:00
|
|
|
import fr.pandacube.util.ListUtil;
|
|
|
|
|
2020-04-06 00:10:14 +02:00
|
|
|
@FunctionalInterface
|
2020-06-08 16:16:18 +02:00
|
|
|
public interface SuggestionsSupplier<S> {
|
2020-04-06 00:10:14 +02:00
|
|
|
|
2020-05-07 18:37:06 +02:00
|
|
|
/**
|
2020-06-08 16:16:18 +02:00
|
|
|
* Number of suggestion visible at once without having to scroll
|
2020-05-07 18:37:06 +02:00
|
|
|
*/
|
|
|
|
public static int VISIBLE_SUGGESTION_COUNT = 10;
|
|
|
|
|
2020-04-06 00:10:14 +02:00
|
|
|
|
|
|
|
public abstract List<String> getSuggestions(S sender, int tokenIndex, String token, String[] args);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static Predicate<String> filter(String token) {
|
|
|
|
return suggestion -> suggestion.toLowerCase().startsWith(token.toLowerCase());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Filter the provided {@link Stream} of string according to the provided token, using the filter returned by {@link #filter(String)},
|
|
|
|
* then returns the strings collected into a {@link List}.
|
|
|
|
*
|
|
|
|
* This methods consume the provided stream, so will not be usable anymore.
|
|
|
|
*/
|
|
|
|
public static List<String> collectFilteredStream(Stream<String> stream, String token) {
|
|
|
|
return stream.filter(filter(token)).sorted().collect(Collectors.toList());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-06-08 16:16:18 +02:00
|
|
|
public static <S> SuggestionsSupplier<S> empty() { return (s, ti, t, a) -> Collections.emptyList(); }
|
2020-04-06 00:10:14 +02:00
|
|
|
|
|
|
|
|
2020-06-08 16:16:18 +02:00
|
|
|
public static <S> SuggestionsSupplier<S> fromCollection(Collection<String> suggestions) {
|
2020-04-06 00:10:14 +02:00
|
|
|
return (s, ti, token, a) -> collectFilteredStream(suggestions.stream(), token);
|
|
|
|
}
|
|
|
|
|
2020-06-08 16:16:18 +02:00
|
|
|
public static <S> SuggestionsSupplier<S> fromArray(String... suggestions) {
|
2020-04-06 00:10:14 +02:00
|
|
|
return (s, ti, token, a) -> collectFilteredStream(Arrays.stream(suggestions), token);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-08 16:16:18 +02:00
|
|
|
public static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnum(Class<E> enumClass) {
|
2020-04-06 00:10:14 +02:00
|
|
|
return fromEnumValues(enumClass.getEnumConstants());
|
|
|
|
}
|
|
|
|
|
2020-06-08 16:16:18 +02:00
|
|
|
public static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnum(Class<E> enumClass, boolean lowerCase) {
|
2020-04-06 00:10:14 +02:00
|
|
|
return fromEnumValues(lowerCase, enumClass.getEnumConstants());
|
|
|
|
}
|
|
|
|
|
|
|
|
@SafeVarargs
|
2020-06-08 16:16:18 +02:00
|
|
|
public static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnumValues(E... enumValues) {
|
2020-04-06 00:10:14 +02:00
|
|
|
return fromEnumValues(false, enumValues);
|
|
|
|
}
|
|
|
|
|
|
|
|
@SafeVarargs
|
2020-06-08 16:16:18 +02:00
|
|
|
public static <E extends Enum<E>, S> SuggestionsSupplier<S> fromEnumValues(boolean lowerCase, E... enumValues) {
|
2020-04-06 00:10:14 +02:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-05-07 18:37:06 +02:00
|
|
|
|
|
|
|
/**
|
2020-06-08 16:16:18 +02:00
|
|
|
* Create a {@link SuggestionsSupplier} that suggest numbers according to the provided range.
|
2020-05-07 18:37:06 +02:00
|
|
|
*
|
|
|
|
* The current implementation only support range that include either -1 or 1.
|
|
|
|
* @param min
|
|
|
|
* @param max
|
|
|
|
* @return
|
|
|
|
*/
|
2020-06-08 16:16:18 +02:00
|
|
|
public static <S> SuggestionsSupplier<S> fromIntRange(int min, int max) {
|
2020-05-07 18:37:06 +02:00
|
|
|
return fromLongRange(min, max);
|
2020-04-06 00:42:07 +02:00
|
|
|
}
|
|
|
|
|
2020-04-06 00:10:14 +02:00
|
|
|
|
|
|
|
|
2020-05-07 18:37:06 +02:00
|
|
|
|
|
|
|
/**
|
2020-06-08 16:16:18 +02:00
|
|
|
* Create a {@link SuggestionsSupplier} that suggest numbers according to the provided range.
|
2020-05-07 18:37:06 +02:00
|
|
|
*
|
|
|
|
* The current implementation only support range that include either -1 or 1.
|
|
|
|
* @param min
|
|
|
|
* @param max
|
|
|
|
* @return
|
|
|
|
*/
|
2020-06-08 16:16:18 +02:00
|
|
|
public static <S> SuggestionsSupplier<S> fromLongRange(long min, long max) {
|
2020-05-07 18:37:06 +02:00
|
|
|
if (max < min) {
|
|
|
|
throw new IllegalArgumentException("min should be less or equals than max");
|
|
|
|
}
|
|
|
|
return (s, ti, token, a) -> {
|
|
|
|
try {
|
|
|
|
List<Long> proposedValues = new ArrayList<>();
|
|
|
|
if (token.length() == 0) {
|
|
|
|
long start = Math.max(Math.max(Math.min(-4, max - 9), min), -9);
|
|
|
|
long end = Math.min(Math.min(start + 9, max), 9);
|
|
|
|
ListUtil.addLongRangeToList(proposedValues, start, end);
|
|
|
|
}
|
|
|
|
else if (token.length() == 1) {
|
|
|
|
if (token.charAt(0) == '0') {
|
|
|
|
if (min > 0 || max < 0) {
|
|
|
|
return Collections.emptyList();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return Collections.singletonList(token);
|
|
|
|
}
|
|
|
|
else if (token.charAt(0) == '-') {
|
|
|
|
ListUtil.addLongRangeToList(proposedValues, Math.max(-9, min), -1);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
long lToken = Long.parseLong(token);
|
|
|
|
if (lToken > max) {
|
|
|
|
return Collections.emptyList();
|
|
|
|
}
|
|
|
|
|
|
|
|
lToken *= 10;
|
|
|
|
if (lToken > max) {
|
|
|
|
return Collections.singletonList(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
ListUtil.addLongRangeToList(proposedValues, lToken, Math.min(lToken + 9, max));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
long lToken = Long.parseLong(token);
|
|
|
|
if (lToken < min || lToken > max) {
|
|
|
|
return Collections.emptyList();
|
|
|
|
}
|
|
|
|
|
|
|
|
lToken *= 10;
|
|
|
|
if (lToken < min || lToken > max) {
|
|
|
|
return Collections.singletonList(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lToken < 0) {
|
|
|
|
ListUtil.addLongRangeToList(proposedValues, Math.max(lToken - 9, min), lToken);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ListUtil.addLongRangeToList(proposedValues, lToken, Math.min(lToken + 9, max));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return collectFilteredStream(proposedValues.stream().map(i -> i.toString()), token);
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
return Collections.emptyList();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-04-06 00:10:14 +02:00
|
|
|
/**
|
2020-06-08 16:16:18 +02:00
|
|
|
* Create a {@link SuggestionsSupplier} that support greedy strings argument using the suggestion from this {@link SuggestionsSupplier}.
|
2020-04-06 00:10:14 +02:00
|
|
|
* @param args all the arguments currently in the buffer
|
|
|
|
* @param index the index of the first argument of the greedy string argument
|
|
|
|
* @return
|
|
|
|
*/
|
2020-06-08 16:16:18 +02:00
|
|
|
public default SuggestionsSupplier<S> greedyString(int index) {
|
2020-04-06 00:10:14 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-06-08 16:16:18 +02:00
|
|
|
public default SuggestionsSupplier<S> requires(Predicate<S> check) {
|
2020-05-07 18:37:06 +02:00
|
|
|
return (s, ti, to, a) -> {
|
|
|
|
return check.test(s) ? getSuggestions(s, ti, to, a) : Collections.emptyList();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-06 00:10:14 +02:00
|
|
|
}
|