123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- /*
- * Copyright (c) 2006-2011 Chris Smith, Shane Mc Cormack, Gregory Holmes
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
- package com.dmdirc.actions;
-
- import java.util.ArrayDeque;
- import java.util.Deque;
-
- /**
- * A condition tree specifies in which order a group of conditions will be
- * executed.
- *
- * @author chris
- */
- public class ConditionTree {
-
- /** The possible operations on a condition tree. */
- public static enum OPERATION {
- /** Only passes if both subtrees are true. */
- AND,
- /** Passes if either of the subtrees are true. */
- OR,
- /** Passes if the specified argument is true. */
- VAR,
- /** Only passes iof the left subtree fails to pass. */
- NOT,
- /** Doesn't do anything (an empty tree). */
- NOOP
- }
-
- /** The left subtree of this tree. */
- private ConditionTree leftArg = null;
-
- /** The right subtree of this tree. */
- private ConditionTree rightArg = null;
-
- /** The argument of this tree (only used for VAR ops). */
- private int argument = -1;
-
- /** The operation that this tree performs. */
- private final OPERATION op;
-
- /**
- * Creates a new ConditionTree for a binary operation.
- *
- * @param op The binary operation to perform
- * @param leftArg The left argument/subtree
- * @param rightArg The right argument/subtree
- */
- private ConditionTree(final OPERATION op, final ConditionTree leftArg,
- final ConditionTree rightArg) {
- this.op = op;
- this.leftArg = leftArg;
- this.rightArg = rightArg;
- }
-
- /**
- * Creates a new ConditionTree for a unary operation.
- *
- * @param op
- * @param argument
- */
- private ConditionTree(final OPERATION op, final ConditionTree argument) {
- this.op = op;
- this.leftArg = argument;
- }
-
- /**
- * Creates a new ConditionTree for a VAR operation with the specified
- * argument number.
- *
- * @param argument The number of the argument that's to be tested.
- */
- private ConditionTree(final int argument) {
- this.op = OPERATION.VAR;
- this.argument = argument;
- }
-
- /**
- * Creates a new ConditionTree for a NOOP operation.
- */
- private ConditionTree() {
- this.op = OPERATION.NOOP;
- }
-
- /**
- * Retrieves the highest argument number that is used in this condition tree.
- *
- * @return The highest argument number used in this tree
- */
- public int getMaximumArgument() {
- if (this.op == OPERATION.NOOP) {
- return 0;
- } else if (this.op == OPERATION.VAR) {
- return argument;
- } else if (this.op == OPERATION.NOT) {
- return leftArg.getMaximumArgument();
- } else {
- return Math.max(leftArg.getMaximumArgument(), rightArg.getMaximumArgument());
- }
- }
-
- /**
- * Evaluates this tree with the specified conditions. Returns the result
- * of the evaluation.
- *
- * @param conditions The binary values of each of the conditions used in
- * this three
- * @return The result of the evaluation of this tree
- */
- public boolean evaluate(final boolean[] conditions) {
- switch (op) {
- case VAR:
- return conditions[argument];
- case NOT:
- return !leftArg.evaluate(conditions);
- case AND:
- return leftArg.evaluate(conditions) && rightArg.evaluate(conditions);
- case OR:
- return leftArg.evaluate(conditions) || rightArg.evaluate(conditions);
- default:
- return true;
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean equals(final Object obj) {
- return obj instanceof ConditionTree
- && toString().equals(((ConditionTree) obj).toString());
- }
-
- /** {@inheritDoc} */
- @Override
- public int hashCode() {
- return toString().hashCode();
- }
-
- /**
- * Retrieves a String representation of this ConditionTree. The string
- * representation is a normalised formula describing this tree and all of
- * its children. The output of this method will generate an identical tree
- * if passed to parseString.
- *
- * @return A string representation of this tree
- */
- @Override
- public String toString() {
- switch (op) {
- case VAR:
- return String.valueOf(argument);
- case NOT:
- return "!" + leftArg;
- case AND:
- return "(" + leftArg + "&" + rightArg + ")";
- case OR:
- return "(" + leftArg + "|" + rightArg + ")";
- default:
- return "";
- }
- }
-
- /**
- * Parses the specified string into a condition tree.
- *
- * @param string The string to be parsed
- * @return The corresponding condition tree, or null if there was an error
- * while parsing the data
- */
- public static ConditionTree parseString(final String string) {
- final Deque<Object> stack = new ArrayDeque<Object>();
-
- for (int i = 0; i < string.length(); i++) {
- final char m = string.charAt(i);
-
- if (isInt(m)) {
- final StringBuilder temp = new StringBuilder(String.valueOf(m));
-
- while (i + 1 < string.length() && isInt(string.charAt(i + 1))) {
- temp.append(string.charAt(i + 1));
- i++;
- }
-
- try {
- stack.add(new ConditionTree(Integer.parseInt(temp.toString())));
- } catch (NumberFormatException ex) {
- return null;
- }
- } else if (m != ' ' && m != '\t' && m != '\n' && m != '\r') {
- stack.add(m);
- }
- }
-
- return parseStack(stack);
- }
-
- /**
- * Parses the specified stack of elements, and returns a corresponding
- * ConditionTree.
- *
- * @param stack The stack to be read.
- * @return The corresponding condition tree, or null if there was an error
- * while parsing the data.
- */
- private static ConditionTree parseStack(final Deque<Object> stack) {
- final Deque<Object> myStack = new ArrayDeque<Object>();
-
- while (!stack.isEmpty()) {
- final Object object = stack.poll();
-
- if (object instanceof Character && ((Character) object) == ')') {
- final ConditionTree bracket = readBracket(myStack);
-
- if (bracket == null) {
- return null;
- } else {
- myStack.add(bracket);
- }
- } else {
- myStack.add(object);
- }
- }
-
- while (!myStack.isEmpty()) {
- if (myStack.size() == 1) {
- final Object first = myStack.pollFirst();
- if (first instanceof ConditionTree) {
- return (ConditionTree) first;
- } else {
- return null;
- }
- }
-
- final ConditionTree first = readTerm(myStack);
-
- if (first == null) {
- return null;
- } else if (myStack.isEmpty()) {
- return first;
- }
-
- final Object second = myStack.pollFirst();
-
- if (myStack.isEmpty()) {
- return null;
- } else {
- final ConditionTree third = readTerm(myStack);
-
- if (third != null && second instanceof Character) {
- OPERATION op;
-
- if ((Character) second == '&') {
- op = OPERATION.AND;
- } else if ((Character) second == '|') {
- op = OPERATION.OR;
- } else {
- return null;
- }
-
- myStack.addFirst(new ConditionTree(op, first, third));
- } else {
- return null;
- }
- }
- }
-
- return new ConditionTree();
- }
-
- /**
- * Reads and returns a single term from the specified stack.
- *
- * @param stack The stack to be read
- * @return The ConditionTree representing the last element on the stack,
- * or null if it was not possible to create one.
- */
- private static ConditionTree readTerm(final Deque<Object> stack) {
- final Object first = stack.pollFirst();
-
- if (first instanceof Character && (Character) first == '!') {
- if (stack.isEmpty()) {
- return null;
- }
-
- return new ConditionTree(OPERATION.NOT, readTerm(stack));
- } else {
- if (!(first instanceof ConditionTree)) {
- return null;
- }
-
- return (ConditionTree) first;
- }
- }
-
- /**
- * Pops elements off of the end of the specified stack until an opening
- * bracket is reached, and then returns the parsed content of the bracket.
- *
- * @param stack The stack to be read for the bracket
- * @return The parsed contents of the bracket, or null if the brackets were
- * mismatched.
- */
- private static ConditionTree readBracket(final Deque<Object> stack) {
- final Deque<Object> tempStack = new ArrayDeque<Object>();
- boolean found = false;
-
- while (!found && !stack.isEmpty()) {
- final Object object = stack.pollLast();
-
- if (object instanceof Character && ((Character) object) == '(') {
- found = true;
- } else {
- tempStack.addFirst(object);
- }
- }
-
- if (found) {
- return parseStack(tempStack);
- } else {
- return null;
- }
- }
-
- /**
- * Determines if the specified character represents a single digit.
- *
- * @param target The character to be tested
- * @return True if the character is a digit, false otherwise
- */
- private static boolean isInt(final char target) {
- return target >= '0' && target <= '9';
- }
-
- /**
- * Creates a condition tree by disjoining the specified number of arguments
- * together.
- *
- * @param numArgs The number of arguments to be disjoined
- * @return The corresponding condition tree
- */
- public static ConditionTree createDisjunction(final int numArgs) {
- final StringBuilder builder = new StringBuilder();
-
- for (int i = 0; i < numArgs; i++) {
- if (builder.length() != 0) {
- builder.append('|');
- }
-
- builder.append(i);
- }
-
- return parseString(builder.toString());
- }
-
- /**
- * Creates a condition tree by conjoining the specified number of arguments
- * together.
- *
- * @param numArgs The number of arguments to be conjoined
- * @return The corresponding condition tree
- */
- public static ConditionTree createConjunction(final int numArgs) {
- final StringBuilder builder = new StringBuilder();
-
- for (int i = 0; i < numArgs; i++) {
- if (builder.length() != 0) {
- builder.append('&');
- }
-
- builder.append(i);
- }
-
- return parseString(builder.toString());
- }
-
- }
|