Added default values, SystemCommandBuilder

Additionally removed sending of SystemCommands as messages and added
sorting of recommendations by relevance.
This commit is contained in:
delvh 2020-07-24 11:09:05 +02:00
parent e49d390089
commit 8718596be2
6 changed files with 347 additions and 294 deletions

View File

@ -1,52 +0,0 @@
package envoy.client.data.commands;
import java.util.function.Function;
import java.util.function.Supplier;
import envoy.client.event.InterruptEvent;
import envoy.event.EventBus;
/**
* This is a Envoy-specific subclass that allows
*
* Project: <strong>envoy-client</strong><br>
* File: <strong>InterruptingSystemCommand.java</strong><br>
* Created: <strong>23.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.2-beta
*/
public class InterruptingSystemCommand extends SystemCommand {
private static final EventBus eventBus = EventBus.getInstance();
/**
* The reason why an {@link InterruptEvent} will be dispatched from this class.
*/
public static final String interruptionReason = "command executed";
/**
* Constructs a new {@code SystemCommand}.
*
* @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 {@code SystemCommand}
* @since Envoy Client v0.2-beta
*/
public InterruptingSystemCommand(Function<String[], Void> action, int numberOfArguments, String description) {
super(action, numberOfArguments, description);
}
@Override
public void onCall() {
super.onCall();
eventBus.dispatch(new InterruptEvent(interruptionReason));
}
@Override
public void onCall(Supplier<Void> consumer) {
super.onCall(consumer);
eventBus.dispatch(new InterruptEvent(interruptionReason));
}
}

View File

@ -1,6 +1,9 @@
package envoy.client.data.commands; package envoy.client.data.commands;
import java.util.function.Function; import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
@ -8,7 +11,7 @@ import java.util.function.Supplier;
* action and a number of arguments that should be used as input for this * action and a number of arguments that should be used as input for this
* function. * function.
* No {@code SystemCommand} can return anything. * No {@code SystemCommand} can return anything.
* Every {@code SystemCommand} must have as argument type {@code String[]} so * Every {@code SystemCommand} must have as argument type {@code List<String>} so
* that the words following the indicator String can be used as input of the * that the words following the indicator String can be used as input of the
* function. This approach has one limitation:<br> * function. This approach has one limitation:<br>
* <b>Order matters!</b> Changing the order of arguments will likely result in * <b>Order matters!</b> Changing the order of arguments will likely result in
@ -23,35 +26,39 @@ import java.util.function.Supplier;
*/ */
public class SystemCommand implements OnCall { public class SystemCommand implements OnCall {
protected int relevance;
/** /**
* The argument count of the command. * The argument count of the command.
*/ */
protected final int numberOfArguments; protected final int numberOfArguments;
/** /**
* This function takes a {@code String[]} as argument because automatically * This function takes a {@code List<String>} as argument because automatically
* {@code SystemCommand#numberOfArguments} words following the necessary command * {@code SystemCommand#numberOfArguments} words following the necessary command
* will be put into this array. * will be put into this list.
* *
* @see String#split(String) * @see String#split(String)
*/ */
protected final Function<String[], Void> action; protected final Consumer<List<String>> action;
protected final String description; protected final String description;
protected int relevance; protected final List<String> defaults;
/** /**
* Constructs a new {@code SystemCommand}. * Constructs a new {@code SystemCommand}.
* *
* @param action the action performed by the command * @param action the action performed by the command
* @param numberOfArguments the argument count accepted by the action * @param numberOfArguments the argument count accepted by the action
* @param defaults the default values for the corresponding arguments
* @param description the description of this {@code SystemCommand} * @param description the description of this {@code SystemCommand}
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public SystemCommand(Function<String[], Void> action, int numberOfArguments, String description) { public SystemCommand(Consumer<List<String>> action, int numberOfArguments, List<String> defaults, String description) {
this.numberOfArguments = numberOfArguments; this.numberOfArguments = numberOfArguments;
this.action = action; this.action = action;
this.defaults = defaults == null ? new ArrayList<>() : defaults;
this.description = description; this.description = description;
} }
@ -59,7 +66,7 @@ public class SystemCommand implements OnCall {
* @return the action that should be performed * @return the action that should be performed
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public Function<String[], Void> getAction() { return action; } public Consumer<List<String>> getAction() { return action; }
/** /**
* @return the argument count of the command * @return the argument count of the command
@ -85,12 +92,44 @@ public class SystemCommand implements OnCall {
*/ */
public void setRelevance(int relevance) { this.relevance = relevance; } public void setRelevance(int relevance) { this.relevance = relevance; }
/**
* Increments the relevance of this {@code SystemCommand}.
*/
@Override @Override
public void onCall() { relevance++; } public void onCall() { relevance++; }
/**
* Increments the relevance of this {@code SystemCommand} and executes the
* supplier.
*/
@Override @Override
public void onCall(Supplier<Void> consumer) { public void onCall(Supplier<Void> consumer) {
onCall(); onCall();
consumer.get(); consumer.get();
} }
/**
* @return the defaults
* @since Envoy Client v0.2-beta
*/
public List<String> getDefaults() { return defaults; }
@Override
public int hashCode() { return Objects.hash(action); }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final SystemCommand other = (SystemCommand) obj;
return Objects.equals(action, other.action);
}
@Override
public String toString() {
return "SystemCommand [relevance=" + relevance + ", numberOfArguments=" + numberOfArguments + ", "
+ (action != null ? "action=" + action + ", " : "") + (description != null ? "description=" + description + ", " : "")
+ (defaults != null ? "defaults=" + defaults : "") + "]";
}
} }

View File

@ -0,0 +1,144 @@
package envoy.client.data.commands;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* This class acts as a builder for {@link SystemCommand}s.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SystemCommandBuilder.java</strong><br>
* Created: <strong>23.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.2-beta
*/
public class SystemCommandBuilder {
private int numberOfArguments;
private Consumer<List<String>> action;
private List<String> defaults;
private String description;
private int relevance;
/**
* @param numberOfArguments the numberOfArguments to set
* @return this {@code SystemCommandBuilder}
* @since Envoy Client v0.2-beta
*/
public SystemCommandBuilder setNumberOfArguments(int numberOfArguments) {
this.numberOfArguments = numberOfArguments;
return this;
}
/**
* @param action the action to set
* @return this {@code SystemCommandBuilder}
* @since Envoy Client v0.2-beta
*/
public SystemCommandBuilder setAction(Consumer<List<String>> action) {
this.action = action;
return this;
}
/**
* @param description the description to set
* @return this {@code SystemCommandBuilder}
* @since Envoy Client v0.2-beta
*/
public SystemCommandBuilder setDescription(String description) {
this.description = description;
return this;
}
/**
* @param relevance the relevance to set
* @return this {@code SystemCommandBuilder}
* @since Envoy Client v0.2-beta
*/
public SystemCommandBuilder setRelevance(int relevance) {
this.relevance = relevance;
return this;
}
/**
* @param defaults the defaults to set
* @return this {@code SystemCommandBuilder}
* @since Envoy Client v0.2-beta
*/
public SystemCommandBuilder setDefaults(String... defaults) {
this.defaults = List.of(defaults);
return this;
}
/**
* Resets all values stored.
*
* @return this {@code SystemCommandBuilder}
* @since Envoy Client v0.2-beta
*/
public SystemCommandBuilder reset() {
numberOfArguments = 0;
action = null;
defaults = new ArrayList<>();
description = "";
relevance = 0;
return this;
}
/**
* Builds a {@code SystemCommand} based upon the previously entered data.
*
* @return the built {@code SystemCommand}
* @since Envoy Client v0.2-beta
*/
public SystemCommand build() { return build(true); }
/**
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
* {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the
* previous value.<br>
* At the end, this {@code SystemCommandBuilder} will be reset.
*
* @return the built {@code SystemCommand}
* @since Envoy Client v0.2-beta
*/
public SystemCommand buildNoArg() {
numberOfArguments = 0;
return build(true);
}
/**
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
* {@code SystemCommand#numberOfArguments} will be set to use the rest of the
* string as argument, regardless of the previous value.<br>
* At the end, this {@code SystemCommandBuilder} will be reset.
*
* @return the built {@code SystemCommand}
* @since Envoy Client v0.2-beta
*/
public SystemCommand buildRemainingArg() {
numberOfArguments = -1;
return build(true);
}
/**
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
* At the end, this {@code SystemCommandBuilder} <b>can</b> be reset but must
* not be.
*
* @param reset whether this {@code SystemCommandBuilder} should be reset
* afterwards.<br>
* This can be useful if another command wants to execute something
* similar
* @return the built {@code SystemCommand}
* @since Envoy Client v0.2-beta
*/
public SystemCommand build(boolean reset) {
final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
sc.setRelevance(relevance);
if (reset) reset();
return sc;
}
}

View File

@ -21,200 +21,46 @@ import envoy.util.EnvoyLog;
*/ */
public final class SystemCommandsMap { public final class SystemCommandsMap {
private final HashMap<String, SystemCommand> systemCommands = new HashMap<>(); private final Map<String, SystemCommand> systemCommands = new HashMap<>();
private final Pattern commandBounds = Pattern.compile("^[a-zA-Z0-9_:!\\(\\)\\?\\.\\,\\;\\-]+$"); private final Pattern commandPattern = Pattern.compile("^[a-zA-Z0-9_:!\\(\\)\\?\\.\\,\\;\\-]+$");
private static final Logger logger = EnvoyLog.getLogger(SystemCommandsMap.class); 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. * Adds a new command to the map if the command name is valid.
* *
* @param command the string that must be inputted to execute the * @param command the input string to execute the
* given action * given action
* @param action the action that should be performed * @param systemCommand the command to add - can be built using
* @param numberOfArguments the amount of arguments that need to be parsed for * {@link SystemCommandBuilder}
* the underlying function
* @see SystemCommandsMap#isValidKey(String) * @see SystemCommandsMap#isValidKey(String)
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public void addCommand(String command, Function<String[], Void> action, int numberOfArguments) { public void add(String command, SystemCommand systemCommand) { if (isValidKey(command)) systemCommands.put(command, systemCommand); }
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 * This method checks if the input String is a key in the map and returns the
* wrapped System command if present. * 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 * 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 * the map, which is a valid case (i.e. input="3/4" and "4" is not a key in the
* map). * map).
* <p> * <p>
* Usage example:<br> * Usage example:<br>
* {@code SystemCommandsMap systemCommands = new SystemCommandsMap();}<br> * {@code SystemCommandsMap systemCommands = new SystemCommandsMap();}<br>
* {@code systemCommands.add("example", Function.identity, 1);}<br> * {@code Button button = new Button();}
* {@code systemCommands.add("example", text -> button.setText(text.get(0), 1);}<br>
* {@code ....}<br> * {@code ....}<br>
* user input: {@code "/example xyz ..."}<br> * user input: {@code "/example xyz ..."}<br>
* {@code systemCommands.checkPresent("example xyz ...")} * {@code systemCommands.get("example xyz ...")} or
* result: {@code SystemCommand[action=Function.identity, numberOfArguments=1]} * {@code systemCommands.get("/example xyz ...")}
* result: {@code Optional<SystemCommand>}
* *
* @param input the input string given by the user, <b>excluding the "/"</b> * @param input the input string given by the user
* @return the wrapped system command, if present * @return the wrapped system command, if present
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public Optional<SystemCommand> checkPresent(String input) { return Optional.ofNullable(systemCommands.get(getCommand(input))); } public Optional<SystemCommand> get(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> * This method ensures that the "/" of a {@link SystemCommand} is stripped.<br>
@ -229,7 +75,52 @@ public final class SystemCommandsMap {
* whatever is entered after the slash is not a system command. Only * whatever is entered after the slash is not a system command. Only
* exception: for recommendation purposes. * exception: for recommendation purposes.
*/ */
public String getCommand(String raw) { return getCommand(raw, 0); } public String getCommand(String raw) {
final var trimmed = raw.stripLeading();
final var index = trimmed.indexOf(' ');
return trimmed.substring(trimmed.charAt(0) == '/' ? 1 : 0, index < 1 ? trimmed.length() : index);
}
/**
* 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 = commandPattern.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 "
+ commandPattern + "are allowed");
return valid;
}
/**
* Takes a 'raw' string (the whole input) and checks if "/" is the first visible
* character and then checks if a command is present after that "/". If that is
* the case, it will be executed.
* <p>
*
* @param raw the raw input string
* @return whether a command could be found
* @since Envoy Client v0.2-beta
*/
public boolean executeIfAnyPresent(String raw) {
// possibly a command was detected and could be executed
final var raw2 = raw.stripLeading();
final var commandFound = raw2.startsWith("/") ? executeIfPresent(raw2) : false;
// the command was executed successfully - no further checking needed
if (commandFound) logger.log(Level.FINE, "executed system command " + getCommand(raw2));
return commandFound;
}
/** /**
* This method checks if the input String is a key in the map and executes the * This method checks if the input String is a key in the map and executes the
@ -242,33 +133,55 @@ public final class SystemCommandsMap {
* Usage example:<br> * Usage example:<br>
* {@code SystemCommandsMap systemCommands = new SystemCommandsMap();}<br> * {@code SystemCommandsMap systemCommands = new SystemCommandsMap();}<br>
* {@code Button button = new Button();}<br> * {@code Button button = new Button();}<br>
* {@code systemCommands.add("example", (words)-> button.setText(words[0]), 1);}<br> * {@code systemCommands.add("example", (words)-> button.setText(words.get(0), 1);}<br>
* {@code ....}<br> * {@code ....}<br>
* user input: {@code "/example xyz ..."}<br> * user input: {@code "/example xyz ..."}<br>
* {@code systemCommands.executeIfPresent("example xyz ...")} * {@code systemCommands.executeIfPresent("example xyz ...")}
* result: {@code button.getText()=="xyz"} * result: {@code button.getText()=="xyz"}
* *
* @param input the input string given by the user, <b>excluding the "/"</b> * @param input the input string given by the user
* @return whether a command could be found
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public void executeIfPresent(String input) { public boolean executeIfPresent(String input) {
checkPresent(input).ifPresent(systemCommand -> { final var command = getCommand(input);
final var value = get(command);
value.ifPresent(systemCommand -> {
// Splitting the String so that the leading command including the first " " is // 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 // removed and only as many following words as allowed by the system command
// persist // persist
final var remainingString = input.substring(input.indexOf(" ") + 1); final var arguments = extractArguments(input, systemCommand);
// 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 // Executing the function
try { try {
systemCommand.getAction().apply(arguments); systemCommand.getAction().accept(arguments);
commandExecuted = true;
systemCommand.onCall(); systemCommand.onCall();
} catch (final Exception e) { } catch (final Exception e) {
logger.log(Level.WARNING, "The system command " + getCommand(input) + " threw an exception: ", e); logger.log(Level.WARNING, "The system command " + command + " threw an exception: ", e);
} }
}); });
return value.isPresent();
}
/**
* Supplies missing values with default values.
*
* @param input the input String
* @param systemCommand the command that is expected
* @return the list of arguments that can be used to parse the systemCommand
* @since Envoy Client v0.2-beta
*/
private List<String> extractArguments(String input, SystemCommand systemCommand) {
// no more arguments follow after the command (e.g. text = "/DABR")
final var indexOfSpace = input.indexOf(" ");
if (indexOfSpace < 0) return supplementDefaults(new String[] {}, systemCommand);
// the arguments behind a system command
final var remainingString = input.substring(indexOfSpace + 1);
final var numberOfArguments = systemCommand.getNumberOfArguments();
// splitting those arguments and supplying default values
final var textArguments = remainingString.split(" ", -1);
final var originalArguments = numberOfArguments >= 0 ? Arrays.copyOfRange(textArguments, 0, numberOfArguments) : textArguments;
final var arguments = supplementDefaults(originalArguments, systemCommand);
return arguments;
} }
/** /**
@ -278,28 +191,13 @@ public final class SystemCommandsMap {
* If none are present, nothing will be done.<br> * If none are present, nothing will be done.<br>
* Otherwise the given function will be executed on the recommendations.<br> * Otherwise the given function will be executed on the recommendations.<br>
* *
* @param input the input * @param input the input string
* @param action the action that should be taken for the recommendations, if any * @param action the action that should be taken for the recommendations, if any
* are present * are present
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public void requestRecommendations(String input, Function<Set<String>, Void> action) { requestRecommendations(input, 0, action); } public void requestRecommendations(String input, Function<Set<String>, Void> action) {
final var partialCommand = getCommand(input);
/**
* 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 // Get the expected commands
final var recommendations = recommendCommands(partialCommand); final var recommendations = recommendCommands(partialCommand);
if (recommendations.isEmpty()) return; if (recommendations.isEmpty()) return;
@ -319,12 +217,47 @@ public final class SystemCommandsMap {
private Set<String> recommendCommands(String partialCommand) { private Set<String> recommendCommands(String partialCommand) {
// current implementation only looks if input is contained within a command, // current implementation only looks if input is contained within a command,
// might be updated // might be updated
return systemCommands.keySet().stream().filter(command -> command.contains(partialCommand)).collect(Collectors.toSet()); return systemCommands.keySet()
.stream()
.filter(command -> command.contains(partialCommand))
.sorted((command1, command2) -> Integer.compare(systemCommands.get(command1).getRelevance(), systemCommands.get(command2).getRelevance()))
.collect(Collectors.toSet());
}
/**
*
* Supplies the default values for arguments if none are present in the text for
* any argument. <br>
* Will only work for {@code SystemCommand}s whose argument counter is bigger
* than 1.
*
* @param textArguments the arguments that were parsed from the text
* @param toEvaluate the system command whose default values should be used
* @return the final argument list
* @since Envoy Client v0.2-beta
* @apiNote this method will insert an empty String if the size of the list
* given to the {@code SystemCommand} is smaller than its argument
* counter and no more text arguments could be found.
*/
private List<String> supplementDefaults(String[] textArguments, SystemCommand toEvaluate) {
final var defaults = toEvaluate.getDefaults();
final var numberOfArguments = toEvaluate.getNumberOfArguments();
final List<String> result = new ArrayList<>();
if (toEvaluate.getNumberOfArguments() > 0) for (int index = 0; index < numberOfArguments; index++) {
String textArg = null;
if (index < textArguments.length) textArg = textArguments[index];
// Set the argument at position index to the current argument of the text, if it
// is present. Otherwise the default for that argument will be taken if present.
// In the worst case, an empty String will be used.
result.add(!(textArg == null) && !textArg.isBlank() ? textArg : index < defaults.size() ? defaults.get(index) : "");
}
return result;
} }
/** /**
* @return all {@link SystemCommand}s used with the underlying command as key * @return all {@link SystemCommand}s used with the underlying command as key
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public HashMap<String, SystemCommand> getSystemCommands() { return systemCommands; } public Map<String, SystemCommand> getSystemCommands() { return systemCommands; }
} }

View File

@ -1,28 +0,0 @@
package envoy.client.event;
import envoy.event.Event;
/**
* This event serves the purpose of interrupting something so that it will not
* be finished.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>InterruptEvent.java</strong><br>
* Created: <strong>23.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.2-beta
*/
public class InterruptEvent extends Event<String> {
private static final long serialVersionUID = 1L;
/**
* Creates a new {@link InterruptEvent}.
*
* @param reason the reason why this event exists -> allows finer sieving
* @since Envoy Client v0.2-beta
*/
public InterruptEvent(String reason) { super(reason); }
}

View File

@ -30,6 +30,7 @@ import javafx.util.Duration;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.data.audio.AudioRecorder; import envoy.client.data.audio.AudioRecorder;
import envoy.client.data.commands.SystemCommandBuilder;
import envoy.client.data.commands.SystemCommandsMap; import envoy.client.data.commands.SystemCommandsMap;
import envoy.client.event.MessageCreationEvent; import envoy.client.event.MessageCreationEvent;
import envoy.client.net.Client; import envoy.client.net.Client;
@ -129,8 +130,6 @@ public final class ChatScene implements Restorable {
// Initialize message and user rendering // Initialize message and user rendering
messageList.setCellFactory(new ListCellFactory<>(MessageControl::new)); messageList.setCellFactory(new ListCellFactory<>(MessageControl::new));
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new)); chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
messageTextAreaCommands.addNoArgCommand("DABR", text -> { doABarrelRoll(); return null; });
messageTextAreaCommands.addNoArgCommand("DoABarrelRoll", text -> { doABarrelRoll(); return null; });
settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE))); settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE))); voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
@ -208,6 +207,22 @@ public final class ChatScene implements Restorable {
}); });
} }
/**
* Initializes all {@code SystemCommands} used in {@code ChatScene}.
*
* @since Envoy Client v0.2-beta
*/
private void initializeSystemCommandsMap() {
final var builder = new SystemCommandBuilder();
// Do A Barrel roll initialization
final var random = new Random();
builder.setAction(text -> doABarrelRoll(Integer.parseInt(text.get(0)), Double.parseDouble(text.get(1))))
.setDefaults(Integer.toString(random.nextInt(3) + 1), Double.toString(random.nextDouble() * 3 + 1))
.setDescription("See for yourself :)")
.setNumberOfArguments(2);
messageTextAreaCommands.add("DABR", builder.build());
}
/** /**
* Initializes all necessary data via dependency injection- * Initializes all necessary data via dependency injection-
* *
@ -230,6 +245,7 @@ public final class ChatScene implements Restorable {
if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info"); if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info");
recorder = new AudioRecorder(); recorder = new AudioRecorder();
initializeSystemCommandsMap();
} }
@Override @Override
@ -381,7 +397,8 @@ public final class ChatScene implements Restorable {
} }
/** /**
* Rotates every element in our application by 360° in at most 2.75s. * Rotates every element in our application by (at most 4 *) 360° in at most
* 2.75s.
* *
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
@ -499,8 +516,8 @@ public final class ChatScene implements Restorable {
return; return;
} }
final var text = messageTextArea.getText().strip(); final var text = messageTextArea.getText().strip();
messageTextAreaCommands.checkForCommands(text); final var systemCommandPresent = messageTextAreaCommands.executeIfAnyPresent(text);
try { if (systemCommandPresent) {} else try {
// Creating the message and its metadata // Creating the message and its metadata
final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator()) final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
.setText(text); .setText(text);