/******************************************************************************* * Copyright (C) 2014 Travis Ralston (turt2live) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package com.turt2live.antishare.object; import com.turt2live.antishare.APermission; import com.turt2live.antishare.engine.list.RejectionList; import com.turt2live.antishare.object.attribute.TrackedState; /** * Represents a command which may be rejected * * @author turt2live */ public class RejectableCommand implements Rejectable, DerivableRejectable, DerivedRejectable { /** * Flag for "starts with". Example: <code>Does "/test command" start with "/test"?</code> */ public static final byte FLAG_STARTS_WITH = 0x01; /** * Flag for "ends with". Example: <code>Does "/test command" end with "command"?</code> */ public static final byte FLAG_ENDS_WITH = 0x02; /** * Flag for "exactly". Example: <code>Is "/test ComAnd" equal to "/test comand"?</code> */ public static final byte FLAG_EXACTLY = 0x04; /** * Flag for "partially found". Example: <code>Does "/test command string" contain "command"?</code> */ public static final byte FLAG_PARTIAL_MATCH = 0x08; /** * Flag for "not found". Example: <code>Does "/test command" contain "not in the command"</code> */ public static final byte FLAG_NO_MATCH = 0x10; private String commandString; private boolean isNegated = false; /** * Creates a new rejectable command * * @param commandString the command string, cannot be null */ public RejectableCommand(String commandString) { if (commandString == null) throw new IllegalArgumentException("command string cannot be null"); if (commandString.startsWith("-")) { isNegated = true; commandString = commandString.substring(1); } if (!commandString.startsWith("/")) commandString = "/" + commandString; this.commandString = commandString; } /** * Determines if this is a negated command or not. Only useful for * list operations. * * @return the negation state of this command */ public boolean isNegated() { return isNegated; } /** * Gets the command string of this rejectable command * * @return the command string */ public String getCommandString() { return commandString; } /** * Determines if a specific player can execute this command. This should * only perform a permissions lookup and nothing more. This is generally * called by the internal logic of AntiShare and will be alongside other * checks such as list inclusion. * <p/> * This uses the tri-state enum {@link com.turt2live.antishare.object.attribute.TrackedState} * to represent various states, as outlined below. * <p/> * {@link com.turt2live.antishare.object.attribute.TrackedState#NOT_PRESENT} - Neither allow or deny permission found<br/> * {@link com.turt2live.antishare.object.attribute.TrackedState#INCLUDED} - Allow permission found<br/> * {@link com.turt2live.antishare.object.attribute.TrackedState#NEGATED} - Deny permission found * * @param player the player to check, cannot be null * * @return the state of this player's permissions as defined */ public TrackedState canExecute(APlayer player) { boolean allow = player.hasPermission(APermission.getPermissionNode(true, RejectionList.ListType.COMMANDS)); boolean deny = player.hasPermission(APermission.getPermissionNode(false, RejectionList.ListType.COMMANDS)); if (allow == deny) return TrackedState.NOT_PRESENT; else if (allow) return TrackedState.INCLUDED; return TrackedState.NEGATED; } /** * Determines if the passed rejectable command matches this rejectable command. * The algorithm applied is <code>Does (this) [flag function] (other)</code>. For * example: <code>Does (this) start with (other)</code>. * <p/> * If multiple flags are provided, a 'vote'-like system takes place where a * majority of flags must match in order to have this return true. For example, * if there are 3 flags applied, 2 (at least) must match in order for this to * return true. Once a minimum has been achieved, no other flags will be checked. * <p/> * This will not consider {@link #isNegated}. * * @param other the other rejectable command, cannot be null * @param flags the flags applicable, unknown flags are ignored * * @return true if this rejectable command matches the other based upon the flags * * @see #FLAG_STARTS_WITH * @see #FLAG_ENDS_WITH * @see #FLAG_EXACTLY * @see #FLAG_PARTIAL_MATCH * @see #FLAG_NO_MATCH */ public boolean matches(RejectableCommand other, byte flags) { if (other == null) throw new IllegalArgumentException("other cannot be null"); int matches = 0; int minMatches = getMinMatches(flags); if ((flags & FLAG_STARTS_WITH) == FLAG_STARTS_WITH && matches < minMatches) { if (this.commandString.startsWith(other.commandString)) matches++; } if ((flags & FLAG_ENDS_WITH) == FLAG_ENDS_WITH && matches < minMatches) { if (this.commandString.endsWith(other.commandString.substring(1))) matches++; } if ((flags & FLAG_EXACTLY) == FLAG_EXACTLY && matches < minMatches) { if (this.commandString.equalsIgnoreCase(other.commandString)) matches++; } if ((flags & FLAG_PARTIAL_MATCH) == FLAG_PARTIAL_MATCH && matches < minMatches) { if (this.commandString.toLowerCase().contains(other.commandString.toLowerCase())) matches++; } if ((flags & FLAG_NO_MATCH) == FLAG_NO_MATCH && matches < minMatches) { if (!this.commandString.toLowerCase().contains(other.commandString.toLowerCase())) matches++; } return matches >= minMatches; } private int getMinMatches(byte flags) { int nflags = 0; if ((flags & FLAG_STARTS_WITH) == FLAG_STARTS_WITH) nflags++; if ((flags & FLAG_ENDS_WITH) == FLAG_ENDS_WITH) nflags++; if ((flags & FLAG_EXACTLY) == FLAG_EXACTLY) nflags++; if ((flags & FLAG_PARTIAL_MATCH) == FLAG_PARTIAL_MATCH) nflags++; if ((flags & FLAG_NO_MATCH) == FLAG_NO_MATCH) nflags++; return (int) Math.ceil(nflags / 2.0); } @Override public String toString() { return "RejectableCommand{" + "commandString='" + commandString + '\'' + ", isNegated=" + isNegated + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof RejectableCommand)) return false; RejectableCommand that = (RejectableCommand) o; if (isNegated != that.isNegated) return false; return commandString.equals(that.commandString); } @Override public int hashCode() { int result = commandString.hashCode(); result = 31 * result + (isNegated ? 1 : 0); return result; } @Override public DerivedRejectable getGeneric() { return null; } @Override public DerivedRejectable getSpecific() { return this; } @Override public boolean hasGeneric() { return false; } }