Skip to content

Instantly share code, notes, and snippets.

@sahirshahryar
Last active December 19, 2015 10:29
Show Gist options
  • Save sahirshahryar/5940796 to your computer and use it in GitHub Desktop.
Save sahirshahryar/5940796 to your computer and use it in GitHub Desktop.
Evaluates stuff.
// A revamped version of the BooleanLogic class, with recursive parenthetical support.
package org.futuredev.tracker.util.lexers.params;
import org.futuredev.tracker.Tracker;
import org.futuredev.tracker.session.user.PlayerSession;
import org.futuredev.tracker.session.user.Session;
import org.futuredev.tracker.util.cmd.CommandProcessing;
import org.futuredev.tracker.util.enums.Permissions;
import org.futuredev.tracker.util.ex.CommandException;
import org.futuredev.tracker.util.lexers.SQLLexer;
import org.futuredev.tracker.util.math.Levenshtein;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.futuredev.tracker.util.math.Vector;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Performs a lexical analysis of the PLAYER parameter.
* This is the most complex lexer here. Maybe even the
* most complex class in this plugin. Well, besides
* ParameterProcessor.
*
* @author afistofirony
*/
public class TargetLexer {
boolean userIsConsole, allowOffline;
ArrayList<String> matches, result, exclusions;
private static final char ALL = '*', SELF = '=', NEAR = '#', MOD = '%',
EXPRESSION = '^', PARTIAL = '&', STARTS = '>', ENDS = '<', WORLD = '$';
enum Logic { AND, OR }
/**
* Parses the given player input.
* @param user The session sending this input.
* @param input The input to parse.
* @param allowOffline Whether or not to allow offline players. This allows this class
* to have dual functionality for both parameters and regular commands.
* @throws CommandException Thrown if an issue is encountered.
*/
public TargetLexer (Session user, String input, boolean allowOffline) throws CommandException {
this.userIsConsole = !(user instanceof PlayerSession);
this.allowOffline = allowOffline;
this.result = new ArrayList<String>();
this.matches = new ArrayList<String>();
this.exclusions = new ArrayList<String>();
for (Player player : Bukkit.getOnlinePlayers()) {
if (this.parseFor(user, player, input)) {
matches.add(player.getName());
continue;
}
exclusions.add(player.getName());
}
for (String match : matches) {
if (!exclusions.contains(match))
result.add(match);
}
}
public String get (int index) {
return result.get(index);
}
public Player getPlayer (int index) {
return Bukkit.getPlayerExact(this.get(index));
}
public PlayerSession getSession (int index) {
return (PlayerSession) Tracker.getServices().getSession(this.get(index));
}
/**
* Recursively parses the input for the given player.
* @param sender The user sending the input.
* @param evaluate The player to check the conditions for.
* @param input The input to parse.
* @return True if the player matches the conditions given.
* @throws CommandException Thrown if an exception is encountered.
*/
private boolean parseFor (Session sender, Player evaluate, String input)
throws CommandException {
String evaluation = "";
int depth = 0;
/* Used to determine how many parentheses are grouped together.
* For example, the ~ in (((~))) has a depth of two.
*/
loop: for (int i = 0; i < input.length(); ++i) { // Get rid of all parentheses.
final char opener = input.charAt(i);
switch (opener) {
case '(':
case '[':
case '{':
++depth;
final char closer = (opener == '(' ? ')' : (opener == '[' ? ']' : '}'));
for (int j = i + 1; j < input.length(); ++j) {
if (input.charAt(j) == closer) {
--depth;
if (depth == 0) {
evaluation += (parseFor(sender, evaluate,
input.substring(i + 1, j - 1)) ? '1' : '0');
i = j;
continue loop;
}
} else switch (input.charAt(j)) {
case '(':
case '[':
case '{':
++depth;
}
}
default:
evaluation += opener;
}
}
String next = "";
for (int i = 0; i < evaluation.length(); ++i) {
switch (evaluation.charAt(i)) {
case ',':
int j;
loop: for (j = i + 1; j < evaluation.length(); ++j) {
switch (evaluation.charAt(j)) {
case ',':
case ';':
break loop;
}
}
next += evaluation.substring(i, j - 1);
next = Character.toString(processGroup(Logic.OR, sender, next, evaluate));
evaluation = evaluation.substring(0, i - 1) + next + evaluation.substring(j);
// The next iteration will increment i anyway. next has become a single character in
// the entire string, so we don't need to do anything here.
break;
case ';':
loop: for (j = i + 1; j < evaluation.length(); ++j) {
switch (evaluation.charAt(j)) {
case ',':
case ';':
break loop;
}
}
next += evaluation.substring(i, j - 1);
if (next.matches("^(!)?[a-zA-Z0-9_]+") && allowOffline) { // Offline match?
if (next.charAt(0) != '!')
result.add(next);
else
exclusions.add(next.substring(1));
continue;
}
next = Character.toString(processGroup(Logic.AND, sender, next, evaluate));
evaluation = evaluation.substring(0, i - 1) + next + evaluation.substring(j);
break;
default: next += evaluation.charAt(i);
}
}
return evaluation.contains("1");
}
private char processGroup (Logic logic, Session user, String statement, Player evaluate)
throws CommandException {
boolean result = false;
ArrayList<String> evaluations = new ArrayList<String>();
switch (logic) {
default:
case OR:
if (!statement.contains(","))
evaluations.add(statement);
else evaluations.addAll(Arrays.asList(statement.split(",")));
for (String condition : evaluations) {
if (condition.matches("^[a-zA-Z0-9]+") && allowOffline)
throw new CommandException("Exception.Lexer.OfflineLogic");
boolean not = condition.charAt(0) == '!';
result = result || (not != processLogic(condition.substring(not ? 1 : 0), user, evaluate));
/* Justification for boolean != boolean
* If a NOT operator is applied, we need to tell the opposite value, so...
* input = !% (not a staff member)
* User we are checking for is a staff member and 'not' is true:
* true != true -> false, just as intended
*/
}
break;
case AND:
result = true; // AND logic needs this to be true
if (!statement.contains(";"))
throw new CommandException("Exception.Lexer.IncompleteLogic");
else evaluations.addAll(Arrays.asList(statement.split(";")));
for (String condition : evaluations) {
if (condition.matches("^[a-zA-Z0-9]+") && allowOffline)
throw new CommandException("Exception.Lexer.OfflineLogic");
boolean not = condition.charAt(0) == '!';
result = result && (not != processLogic(condition, user, evaluate));
}
}
return result ? '1' : '0';
}
/**
* Processes the logic given.
* @param value The input.
* @param sender The sender of the input.
* @param target The player to evaluate.
* @return Whether or not the given player matches the given logic.
* @throws CommandException Thrown if an issue is encountered.
*/
private boolean processLogic (String value, Session sender, Player target) throws CommandException {
if (value.equals("1") || value.equals("!0")) return true;
if (value.equals("0") || value.equals("!1")) return false;
switch (value.length()) {
case 0:
return false;
case 1:
switch (value.charAt(0)) {
case ALL:
sender.authorise(Permissions.TARGETING_ALL);
this.exclusions.clear(); // Clear exclusions, this specifies ALL!
return true;
case WORLD:
sender.authorise(Permissions.TARGETING_WORLD);
CommandProcessing.restrict(sender);
LocationLexer lexer = new LocationLexer(sender,
value.substring(1), null, false, -1);
return lexer.withinArea(target);
case SELF:
return !userIsConsole;
// Not evaluating the player var here, so sender needs to be a player.
case NEAR:
sender.authorise(Permissions.TARGETING_NEAR);
CommandProcessing.restrict(sender);
PlayerSession player = (PlayerSession) sender;
return player.getSurroundingArea(15).
contains(Vector.fromBukkitVector(target.getLocation().toVector()));
case MOD: // It's funny because modulus.
sender.authorise(Permissions.TARGETING_STAFF);
return sender.hasPermission(Permissions.MOD);
default:
return false;
}
default:
switch (value.charAt(0)) {
case WORLD:
sender.authorise(Permissions.TARGETING_WORLD);
LocationLexer lexer = new LocationLexer(sender,
value.substring(1), null, false, -1);
return lexer.withinArea(target);
case NEAR:
sender.authorise(Permissions.TARGETING_NEAR);
lexer = new LocationLexer(sender,
value.substring(1), null, false, -1);
return lexer.withinArea(target);
case STARTS:
sender.authorise(Permissions.TARGETING_STARTS_WITH);
return target.getName().toLowerCase().startsWith(value.substring(1).toLowerCase());
case ENDS:
sender.authorise(Permissions.TARGETING_ENDS_WITH);
return target.getName().toLowerCase().endsWith(value.substring(1).toLowerCase());
case EXPRESSION:
sender.authorise(Permissions.TARGETING_REGEX);
return target.getName().matches(value.substring(1));
case PARTIAL:
if (allowOffline) {
sender.authorise(Permissions.TARGETING_PARTIAL);
return target.getName().contains(value.substring(1)) ||
new Levenshtein(value.substring(1), target.getName()).getDistance() <=
(Tracker.getSettings().lev ? Tracker.getSettings().levLimit : 0);
} else {
return target.getName().equalsIgnoreCase(value.substring(1));
}
default:
return !allowOffline &&
(target.getName().contains(value) ||
new Levenshtein(value, target.getName()).getDistance() <=
(Tracker.getSettings().lev ? Tracker.getSettings().levLimit : 0));
// We handle offline matching separately anyways.
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment