Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package dev.jorel.commandapi.arguments;
import com.mojang.brigadier.LiteralMessage;
import com.mojang.brigadier.Message;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import dev.jorel.commandapi.executors.CommandArguments;
import java.util.*;
import java.util.concurrent.CompletableFuture;
/**
* An argument that represents a key-value pair.
*
* @param The type of keys this map will contain
* @param The type of values this map will contain
* @apiNote Returns a {@link LinkedHashMap} object
* @since 9.0.0
*/
@SuppressWarnings("rawtypes")
public class MapArgument extends Argument implements GreedyArgument {
private final String delimiter;
private final String separator;
private final StringParser keyMapper;
private final StringParser valueMapper;
private final ResultList keyList;
private final ResultList valueList;
private final boolean allowValueDuplicates;
private final boolean keyListEmpty;
private final boolean valueListEmpty;
/**
* Constructs a {@link MapArgument}
*
* @param nodeName the name to assign to this argument node
* @param delimiter This is used to separate key-value pairs
*/
MapArgument(String nodeName, String delimiter, String separator, StringParser keyMapper, StringParser valueMapper, List keyList, List valueList, boolean allowValueDuplicates) {
super(nodeName, StringArgumentType.greedyString());
this.delimiter = delimiter;
this.separator = separator;
this.keyMapper = keyMapper;
this.valueMapper = valueMapper;
this.keyList = ResultList.formatResults(keyList, delimiter);
this.valueList = ResultList.formatResults(valueList, separator);
this.allowValueDuplicates = allowValueDuplicates;
this.keyListEmpty = keyList == null;
this.valueListEmpty = valueList == null;
applySuggestions();
}
private void applySuggestions() {
super.replaceSuggestions((info, builder) -> {
StringReader reader = new StringReader(info.currentArg());
// Read through the keys and values
Set givenKeys = new HashSet<>();
Set givenValues = new HashSet<>();
List unusedKeys = new ArrayList<>(keyList.results);
List unusedValues = new ArrayList<>(valueList.results);
boolean isKey = true;
while (reader.canRead()) {
boolean isQuoted = reader.peek() == '"';
String result;
try {
result = isQuoted ? readQuoted(reader, isKey) : readUnquoted(reader, isKey);
} catch (CommandSyntaxException ignored) {
// Exception is thrown when the key/value never terminates
// That means this key/value ends the argument, so we should do the suggestions now
builder = builder.createOffset(builder.getStart() + reader.getCursor() - (isQuoted ? 1 : 0));
if (!(isKey ? keyListEmpty : valueListEmpty)) {
return doResultSuggestions(readEscapedUntilEnd(reader), builder, isKey ? unusedKeys : unusedValues, isKey, isQuoted);
}
return doEmptySuggestions(reader.getRemaining(), builder, isKey, isQuoted);
}
if (!(isKey ? keyListEmpty : valueListEmpty)) {
// Enforce the lists if they are not empty
List relaventList = isKey ? unusedKeys : unusedValues;
if (!relaventList.contains(result)) {
throw invalidResult(result, reader, isKey, isQuoted);
}
if (isKey || !allowValueDuplicates) {
relaventList.remove(result);
}
} else if ((isKey || !allowValueDuplicates) && !(isKey ? givenKeys : givenValues).add(result)) {
// If no lists given, we still enforce duplicates using the 'given' sets
throw invalidResult(result, reader, isKey, isQuoted);
}
// Make sure result is valid according to the parsers
try {
if (isKey) {
keyMapper.parse(result);
} else {
valueMapper.parse(result);
}
} catch (Exception e) {
throw handleParserException(e, result, reader, isKey, isQuoted);
}
// Handle separator
String relevantSeparator = isKey ? delimiter : separator;
if (!reader.canRead(relevantSeparator.length())) {
// Argument ends at a separator
// If the separator is being typed correctly, suggest they keep going
// If the separator is being typed incorrectly, this suggests overriding with the correct separator
builder = builder.createOffset(builder.getStart() + reader.getCursor());
builder.suggest(relevantSeparator);
return builder.buildFuture();
} else {
// Argument seems to keep going, validate separator
int start = reader.getCursor();
reader.setCursor(start + relevantSeparator.length());
String typedSeparator = reader.getString().substring(start, reader.getCursor());
if (!relevantSeparator.equals(typedSeparator)) {
reader.setCursor(start); // Set cursor back to start to underline bad typed separator
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().createWithContext(reader, separatorRequiredMessage(isKey));
}
// All good, keep going
}
// Move to next key/value
isKey = !isKey;
}
// We reached the end exactly when a key/value and its terminator ended
// Start suggestions for the next key/value
return startSuggestions(builder, isKey ? unusedKeys : unusedValues, isKey);
});
}
private CompletableFuture startSuggestions(SuggestionsBuilder builder, List unusedResults, boolean isKey) {
// Nothing written yet, give the preferred suggestions
builder = builder.createOffset(builder.getStart() + builder.getRemaining().length());
ResultList relevantList = isKey ? keyList : valueList;
for (String result : unusedResults) {
// We either prefer quoted or unquoted, so this should only suggest 1 per result
String unquotedSuggestion = relevantList.preferredUnquoted.get(result);
if (unquotedSuggestion != null) {
builder.suggest(unquotedSuggestion);
}
String quotedSuggestion = relevantList.preferredQuoted.get(result);
if (quotedSuggestion != null) {
builder.suggest('"' + quotedSuggestion + '"');
}
}
return builder.buildFuture();
}
private CompletableFuture doResultSuggestions(String ending, SuggestionsBuilder builder, List unusedResults, boolean isKey, boolean isQuoted) {
String quotedInsert = isQuoted ? "\"" : "";
String relevantSeparator = isKey ? delimiter : separator;
ResultList relevantList = isKey ? keyList : valueList;
Map suggestionsMap = isQuoted ? relevantList.quoted : relevantList.unquoted;
// Suggest key/value if they fit
for (String result : unusedResults) {
// If result starts with ending, and they are the same length, they must be equal
boolean sameLength = result.length() == ending.length();
if (result.startsWith(ending)) {
// Started typing one of the results
// If they are equal, the key/value is complete, so we should also add the separator
builder.suggest(quotedInsert + suggestionsMap.get(result) + quotedInsert + (sameLength ? relevantSeparator : ""));
}
if (!sameLength && ending.startsWith(result)) {
// Typed a value result, then attempted to start the separator
// Always suggest the separator following because it is necessary
builder.suggest(quotedInsert + suggestionsMap.get(result) + quotedInsert + relevantSeparator);
}
}
return builder.buildFuture();
}
private CompletableFuture doEmptySuggestions(String ending, SuggestionsBuilder builder, boolean isKey, boolean isQuoted) {
// If the result isn't from a set list, always suggest completing it with the terminator at the end
String suggestion = ending;
int length = suggestion.length();
if (length != 0 && suggestion.charAt(length - 1) == '\\') {
boolean escaped = false;
int i = length - 2;
while (i >= 0) {
if (suggestion.charAt(i) != '\\') {
break;
}
i--;
escaped = !escaped;
}
// If there is an unescaped \ at the end, suggest another backslash to eat up its effect
if (!escaped) {
suggestion += '\\';
}
}
// Add quotes around suggestion
if (isQuoted) {
suggestion = "\"" + suggestion + "\"";
}
// Add terminator
suggestion = suggestion + (isKey ? delimiter : separator);
builder.suggest(suggestion);
return builder.buildFuture();
}
@Override
public Class getPrimitiveType() {
return LinkedHashMap.class;
}
@Override
public CommandAPIArgumentType getArgumentType() {
return CommandAPIArgumentType.MAP;
}
@Override
public