331 lines
14 KiB
Java
331 lines
14 KiB
Java
package envoy.client.data.commands;
|
|
|
|
import java.util.*;
|
|
import java.util.function.Function;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import java.util.regex.Pattern;
|
|
import java.util.stream.Collectors;
|
|
|
|
import envoy.util.EnvoyLog;
|
|
|
|
/**
|
|
* This class stores all {@link SystemCommand}s used.
|
|
* <p>
|
|
* Project: <strong>envoy-client</strong><br>
|
|
* File: <strong>SystemCommandsMap.java</strong><br>
|
|
* Created: <strong>17.07.2020</strong><br>
|
|
*
|
|
* @author Leon Hofmeister
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public final class SystemCommandsMap {
|
|
|
|
private final HashMap<String, SystemCommand> systemCommands = new HashMap<>();
|
|
|
|
private final Pattern commandBounds = Pattern.compile("^[a-zA-Z0-9_:!\\(\\)\\?\\.\\,\\;\\-]+$");
|
|
|
|
private static final Logger logger = EnvoyLog.getLogger(SystemCommandsMap.class);
|
|
|
|
private boolean commandExecuted;
|
|
|
|
/**
|
|
* Adds a new command to the map if the command name is valid.
|
|
*
|
|
* @param command the string that must be inputted to execute the
|
|
* given action
|
|
* @param action the action that should be performed
|
|
* @param numberOfArguments the amount of arguments that need to be parsed for
|
|
* the underlying function
|
|
* @see SystemCommandsMap#isValidKey(String)
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public void addCommand(String command, Function<String[], Void> action, int numberOfArguments) {
|
|
if (isValidKey(command)) systemCommands.put(command, new SystemCommand(action, numberOfArguments, ""));
|
|
}
|
|
|
|
/**
|
|
* Adds a command with according action, the number of arguments that should be
|
|
* parsed and a description of the system command, if the command does not
|
|
* violate API constrictions, to the map.
|
|
*
|
|
* @param command the string that must be inputted to execute the
|
|
* given action
|
|
* @param action the action that should be performed
|
|
* @param numberOfArguments the amount of arguments that need to be parsed for
|
|
* the underlying function
|
|
* @param description the description of this {@link SystemCommand}
|
|
* @see SystemCommandsMap#isValidKey(String)
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public void addCommand(String command, Function<String[], Void> action, int numberOfArguments, String description) {
|
|
if (isValidKey(command)) systemCommands.put(command, new SystemCommand(action, numberOfArguments, description));
|
|
}
|
|
|
|
/**
|
|
* Adds a command with according action that does not depend on arguments, if
|
|
* the command does not violate API constrictions, to the map.
|
|
*
|
|
* @param command the string that must be inputted to execute the given action
|
|
* @param action the action that should be performed. To see why this Function
|
|
* takes a {@code String[]}, see {@link SystemCommand}
|
|
* @see SystemCommandsMap#isValidKey(String)
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public void addNoArgCommand(String command, Function<String[], Void> action) { addCommand(command, action, 0); }
|
|
|
|
/**
|
|
* Adds a command with according action that does not depend on arguments and a
|
|
* description of that action, if the command does not violate API
|
|
* constrictions, to the map.
|
|
*
|
|
* @param command the string that must be inputted to execute the given
|
|
* action
|
|
* @param action the action that should be performed. To see why this
|
|
* Function takes a {@code String[]}, see
|
|
* {@link SystemCommand}
|
|
* @param description the description of this {@link SystemCommand}
|
|
* @see SystemCommandsMap#isValidKey(String)
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public void addNoArgCommand(String command, Function<String[], Void> action, String description) { addCommand(command, action, 0); }
|
|
|
|
/**
|
|
* Convenience method that does the same as
|
|
* {@link SystemCommandsMap#addCommand(String, Function, int)}.
|
|
* <p>
|
|
* Adds a command with according action and the number of arguments that should
|
|
* be parsed if the command does not violate API constrictions to the map.
|
|
*
|
|
* @param command the string that must be inputted to execute the
|
|
* given action
|
|
* @param action the action that should be performed. To see why this
|
|
* Function takes a {@code String[]}, see
|
|
* {@link SystemCommand}
|
|
* @param numberOfArguments the amount of arguments that need to be parsed for
|
|
* the underlying function
|
|
* @see SystemCommandsMap#isValidKey(String)
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public void add(String command, Function<String[], Void> action, int numberOfArguments) { addCommand(command, action, numberOfArguments); }
|
|
|
|
/**
|
|
* Examines whether a key can be put in the map and logs it with
|
|
* {@code Level.WARNING} if that key violates API constrictions <br>
|
|
* (allowed chars are <b>a-zA-Z0-9_:!()?.,;-</b>)
|
|
* <p>
|
|
* The approach to not throw an exception was taken so that an ugly try-catch
|
|
* block for every addition to the system commands map could be avoided, an
|
|
* error that
|
|
* should only occur during implementation and not in production.
|
|
*
|
|
* @param command the key to examine
|
|
* @return whether this key can be used in the map
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public boolean isValidKey(String command) {
|
|
final boolean valid = commandBounds.matcher(command).matches();
|
|
if (!valid) logger
|
|
.log(Level.WARNING,
|
|
"The command \"" + command
|
|
+ "\" is not valid. As it will cause problems in execution, it will not be entered into the map. Only the characters "
|
|
+ commandBounds + "are allowed");
|
|
return valid;
|
|
}
|
|
|
|
/**
|
|
* This method checks if the input String is a key in the map and returns the
|
|
* wrapped System command if present.
|
|
* Its intended usage is after a "/" has been detected in the input String.
|
|
* It will return an empty optional if the value after the slash is not a key in
|
|
* the map, which is a valid case (i.e. input="3/4" and "4" is not a key in the
|
|
* map).
|
|
* <p>
|
|
* Usage example:<br>
|
|
* {@code SystemCommandsMap systemCommands = new SystemCommandsMap();}<br>
|
|
* {@code systemCommands.add("example", Function.identity, 1);}<br>
|
|
* {@code ....}<br>
|
|
* user input: {@code "/example xyz ..."}<br>
|
|
* {@code systemCommands.checkPresent("example xyz ...")}
|
|
* result: {@code SystemCommand[action=Function.identity, numberOfArguments=1]}
|
|
*
|
|
* @param input the input string given by the user, <b>excluding the "/"</b>
|
|
* @return the wrapped system command, if present
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public Optional<SystemCommand> checkPresent(String input) { return Optional.ofNullable(systemCommands.get(getCommand(input))); }
|
|
|
|
/**
|
|
* Takes a 'raw' string (the whole input) and checks if a command is present
|
|
* after a "/". If that is the case, it will be executed.
|
|
* <p>
|
|
* Only one system command can be present, afterwards checking will not be
|
|
* continued.
|
|
*
|
|
* @param raw the raw input string
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public void checkForCommands(String raw) { checkForCommands(raw, 0); }
|
|
|
|
/**
|
|
* Takes a 'raw' string (the whole input) and checks from {@code fromIndex} on
|
|
* if a command is present
|
|
* after a "/". If that is the case, it will be executed.
|
|
* <p>
|
|
* Only one system command can be present, afterwards checking will not be
|
|
* continued.
|
|
*
|
|
* @param raw the raw input string
|
|
* @param fromIndex the index to start checking on
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public void checkForCommands(String raw, int fromIndex) {
|
|
// The minimum length of a command is "/" + a letter, hence raw.length()-2 is
|
|
// the highest index needed
|
|
for (int i = fromIndex; i < raw.length() - 2; i++)
|
|
// possibly a command was detected
|
|
if (raw.charAt(i) == '/') {
|
|
executeIfPresent(getCommand(raw, i));
|
|
// the command was executed successfully - no further checking needed
|
|
if (commandExecuted) {
|
|
commandExecuted = false;
|
|
logger.log(Level.FINE, "executed system command " + getCommand(raw, i));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method ensures that the "/" of a {@link SystemCommand} is stripped.<br>
|
|
* It returns the command as (most likely) entered as key in the map starting
|
|
* from {@code fromIndex}.<br>
|
|
* It should only be called on strings that contain a "/" .
|
|
*
|
|
* @param raw the input
|
|
* @param fromIndex the index from which to expect the system command -
|
|
* regardless of whether the slash is still present at this
|
|
* index
|
|
* @return the command as entered in the map
|
|
* @since Envoy Client v0.2-beta
|
|
* @apiNote this method will (most likely) not return anything useful if
|
|
* whatever is entered after the slash is not a system command. Only
|
|
* exception: for recommendation purposes.
|
|
*/
|
|
public String getCommand(String raw, int fromIndex) {
|
|
final var index = raw.indexOf(' ');
|
|
return raw.substring(fromIndex + raw.charAt(0) == '/' ? 1 : 0, index < 1 ? raw.length() : index);
|
|
}
|
|
|
|
/**
|
|
* This method ensures that the "/" of a {@link SystemCommand} is stripped.<br>
|
|
* It returns the command as (most likely) entered as key in the map for the
|
|
* first word of the text.<br>
|
|
* It should only be called on strings that contain a "/" at position 0/-1.
|
|
*
|
|
* @param raw the input
|
|
* @return the command as entered in the map
|
|
* @since Envoy Client v0.2-beta
|
|
* @apiNote this method will (most likely) not return anything useful if
|
|
* whatever is entered after the slash is not a system command. Only
|
|
* exception: for recommendation purposes.
|
|
*/
|
|
public String getCommand(String raw) { return getCommand(raw, 0); }
|
|
|
|
/**
|
|
* This method checks if the input String is a key in the map and executes the
|
|
* wrapped System command if present.
|
|
* Its intended usage is after a "/" has been detected in the input String.
|
|
* It will do nothing if the value after the slash is not a key in
|
|
* the map, which is a valid case (i.e. input="3/4" and "4" is not a key in the
|
|
* map).
|
|
* <p>
|
|
* Usage example:<br>
|
|
* {@code SystemCommandsMap systemCommands = new SystemCommandsMap();}<br>
|
|
* {@code Button button = new Button();}<br>
|
|
* {@code systemCommands.add("example", (words)-> button.setText(words[0]), 1);}<br>
|
|
* {@code ....}<br>
|
|
* user input: {@code "/example xyz ..."}<br>
|
|
* {@code systemCommands.executeIfPresent("example xyz ...")}
|
|
* result: {@code button.getText()=="xyz"}
|
|
*
|
|
* @param input the input string given by the user, <b>excluding the "/"</b>
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public void executeIfPresent(String input) {
|
|
checkPresent(input).ifPresent(systemCommand -> {
|
|
// Splitting the String so that the leading command including the first " " is
|
|
// removed and only as many following words as allowed by the system command
|
|
// persist
|
|
final var remainingString = input.substring(input.indexOf(" ") + 1);
|
|
// TODO: Current implementation will fail in certain cases, i.e. two spaces
|
|
// behind each other (" "), not enough words, ...
|
|
final var arguments = Arrays.copyOfRange(remainingString.split(" "), 0, systemCommand.getNumberOfArguments());
|
|
// Executing the function
|
|
try {
|
|
systemCommand.getAction().apply(arguments);
|
|
commandExecuted = true;
|
|
systemCommand.onCall();
|
|
} catch (final Exception e) {
|
|
logger.log(Level.WARNING, "The system command " + getCommand(input) + " threw an exception: ", e);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Retrieves the recommendations based on the current input entered.<br>
|
|
* The first word is used for the recommendations and
|
|
* it does not matter if the "/" is at its beginning or not.<br>
|
|
* If none are present, nothing will be done.<br>
|
|
* Otherwise the given function will be executed on the recommendations.<br>
|
|
*
|
|
* @param input the input
|
|
* @param action the action that should be taken for the recommendations, if any
|
|
* are present
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public void requestRecommendations(String input, Function<Set<String>, Void> action) { requestRecommendations(input, 0, action); }
|
|
|
|
/**
|
|
* Retrieves the recommendations based on the current input entered.<br>
|
|
* The word beginning at {@code fromIndex} is used for the recommendations and
|
|
* it does not matter if the "/" is at its beginning or not.<br>
|
|
* If none are present, nothing will be done.<br>
|
|
* Otherwise the given function will be executed on the recommendations.<br>
|
|
*
|
|
* @param input the input
|
|
* @param fromIndex the index to start checking on
|
|
* @param action the action that should be taken for the recommendations, if
|
|
* any are present
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
private void requestRecommendations(String input, int fromIndex, Function<Set<String>, Void> action) {
|
|
final var partialCommand = getCommand(input, fromIndex);
|
|
// Get the expected commands
|
|
final var recommendations = recommendCommands(partialCommand);
|
|
if (recommendations.isEmpty()) return;
|
|
// Execute the given action
|
|
else action.apply(recommendations);
|
|
}
|
|
|
|
/**
|
|
* Recommends commands based upon the currently entered input.<br>
|
|
* In the current implementation, all we check is whether a key contains this
|
|
* input. This might be updated later on.
|
|
*
|
|
* @param partialCommand the partially entered command
|
|
* @return a set of all commands that match this input
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
private Set<String> recommendCommands(String partialCommand) {
|
|
// current implementation only looks if input is contained within a command,
|
|
// might be updated
|
|
return systemCommands.keySet().stream().filter(command -> command.contains(partialCommand)).collect(Collectors.toSet());
|
|
}
|
|
|
|
/**
|
|
* @return all {@link SystemCommand}s used with the underlying command as key
|
|
* @since Envoy Client v0.2-beta
|
|
*/
|
|
public HashMap<String, SystemCommand> getSystemCommands() { return systemCommands; }
|
|
}
|