You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ConditionTree.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. /*
  2. * Copyright (c) 2006-2012 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.actions;
  23. import java.util.ArrayDeque;
  24. import java.util.Deque;
  25. /**
  26. * A condition tree specifies in which order a group of conditions will be
  27. * executed.
  28. */
  29. public class ConditionTree {
  30. /** The possible operations on a condition tree. */
  31. public static enum OPERATION {
  32. /** Only passes if both subtrees are true. */
  33. AND,
  34. /** Passes if either of the subtrees are true. */
  35. OR,
  36. /** Passes if the specified argument is true. */
  37. VAR,
  38. /** Only passes iof the left subtree fails to pass. */
  39. NOT,
  40. /** Doesn't do anything (an empty tree). */
  41. NOOP
  42. }
  43. /** The left subtree of this tree. */
  44. private ConditionTree leftArg = null;
  45. /** The right subtree of this tree. */
  46. private ConditionTree rightArg = null;
  47. /** The argument of this tree (only used for VAR ops). */
  48. private int argument = -1;
  49. /** The operation that this tree performs. */
  50. private final OPERATION op;
  51. /**
  52. * Creates a new ConditionTree for a binary operation.
  53. *
  54. * @param op The binary operation to perform
  55. * @param leftArg The left argument/subtree
  56. * @param rightArg The right argument/subtree
  57. */
  58. private ConditionTree(final OPERATION op, final ConditionTree leftArg,
  59. final ConditionTree rightArg) {
  60. this.op = op;
  61. this.leftArg = leftArg;
  62. this.rightArg = rightArg;
  63. }
  64. /**
  65. * Creates a new ConditionTree for a unary operation.
  66. *
  67. * @param op
  68. * @param argument
  69. */
  70. private ConditionTree(final OPERATION op, final ConditionTree argument) {
  71. this.op = op;
  72. this.leftArg = argument;
  73. }
  74. /**
  75. * Creates a new ConditionTree for a VAR operation with the specified
  76. * argument number.
  77. *
  78. * @param argument The number of the argument that's to be tested.
  79. */
  80. private ConditionTree(final int argument) {
  81. this.op = OPERATION.VAR;
  82. this.argument = argument;
  83. }
  84. /**
  85. * Creates a new ConditionTree for a NOOP operation.
  86. */
  87. private ConditionTree() {
  88. this.op = OPERATION.NOOP;
  89. }
  90. /**
  91. * Retrieves the highest argument number that is used in this condition tree.
  92. *
  93. * @return The highest argument number used in this tree
  94. */
  95. public int getMaximumArgument() {
  96. if (this.op == OPERATION.NOOP) {
  97. return 0;
  98. } else if (this.op == OPERATION.VAR) {
  99. return argument;
  100. } else if (this.op == OPERATION.NOT) {
  101. return leftArg.getMaximumArgument();
  102. } else {
  103. return Math.max(leftArg.getMaximumArgument(), rightArg.getMaximumArgument());
  104. }
  105. }
  106. /**
  107. * Evaluates this tree with the specified conditions. Returns the result
  108. * of the evaluation.
  109. *
  110. * @param conditions The binary values of each of the conditions used in
  111. * this three
  112. * @return The result of the evaluation of this tree
  113. */
  114. public boolean evaluate(final boolean[] conditions) {
  115. switch (op) {
  116. case VAR:
  117. return conditions[argument];
  118. case NOT:
  119. return !leftArg.evaluate(conditions);
  120. case AND:
  121. return leftArg.evaluate(conditions) && rightArg.evaluate(conditions);
  122. case OR:
  123. return leftArg.evaluate(conditions) || rightArg.evaluate(conditions);
  124. default:
  125. return true;
  126. }
  127. }
  128. /** {@inheritDoc} */
  129. @Override
  130. public boolean equals(final Object obj) {
  131. return obj instanceof ConditionTree
  132. && toString().equals(((ConditionTree) obj).toString());
  133. }
  134. /** {@inheritDoc} */
  135. @Override
  136. public int hashCode() {
  137. return toString().hashCode();
  138. }
  139. /**
  140. * Retrieves a String representation of this ConditionTree. The string
  141. * representation is a normalised formula describing this tree and all of
  142. * its children. The output of this method will generate an identical tree
  143. * if passed to parseString.
  144. *
  145. * @return A string representation of this tree
  146. */
  147. @Override
  148. public String toString() {
  149. switch (op) {
  150. case VAR:
  151. return String.valueOf(argument);
  152. case NOT:
  153. return "!" + leftArg;
  154. case AND:
  155. return "(" + leftArg + "&" + rightArg + ")";
  156. case OR:
  157. return "(" + leftArg + "|" + rightArg + ")";
  158. default:
  159. return "";
  160. }
  161. }
  162. /**
  163. * Parses the specified string into a condition tree.
  164. *
  165. * @param string The string to be parsed
  166. * @return The corresponding condition tree, or null if there was an error
  167. * while parsing the data
  168. */
  169. public static ConditionTree parseString(final String string) {
  170. final Deque<Object> stack = new ArrayDeque<Object>();
  171. for (int i = 0; i < string.length(); i++) {
  172. final char m = string.charAt(i);
  173. if (isInt(m)) {
  174. final StringBuilder temp = new StringBuilder(String.valueOf(m));
  175. while (i + 1 < string.length() && isInt(string.charAt(i + 1))) {
  176. temp.append(string.charAt(i + 1));
  177. i++;
  178. }
  179. try {
  180. stack.add(new ConditionTree(Integer.parseInt(temp.toString())));
  181. } catch (NumberFormatException ex) {
  182. return null;
  183. }
  184. } else if (m != ' ' && m != '\t' && m != '\n' && m != '\r') {
  185. stack.add(m);
  186. }
  187. }
  188. return parseStack(stack);
  189. }
  190. /**
  191. * Parses the specified stack of elements, and returns a corresponding
  192. * ConditionTree.
  193. *
  194. * @param stack The stack to be read.
  195. * @return The corresponding condition tree, or null if there was an error
  196. * while parsing the data.
  197. */
  198. private static ConditionTree parseStack(final Deque<Object> stack) {
  199. final Deque<Object> myStack = new ArrayDeque<Object>();
  200. while (!stack.isEmpty()) {
  201. final Object object = stack.poll();
  202. if (object instanceof Character && ((Character) object) == ')') {
  203. final ConditionTree bracket = readBracket(myStack);
  204. if (bracket == null) {
  205. return null;
  206. } else {
  207. myStack.add(bracket);
  208. }
  209. } else {
  210. myStack.add(object);
  211. }
  212. }
  213. while (!myStack.isEmpty()) {
  214. if (myStack.size() == 1) {
  215. final Object first = myStack.pollFirst();
  216. if (first instanceof ConditionTree) {
  217. return (ConditionTree) first;
  218. } else {
  219. return null;
  220. }
  221. }
  222. final ConditionTree first = readTerm(myStack);
  223. if (first == null) {
  224. return null;
  225. } else if (myStack.isEmpty()) {
  226. return first;
  227. }
  228. final Object second = myStack.pollFirst();
  229. if (myStack.isEmpty()) {
  230. return null;
  231. } else {
  232. final ConditionTree third = readTerm(myStack);
  233. if (third != null && second instanceof Character) {
  234. OPERATION op;
  235. if ((Character) second == '&') {
  236. op = OPERATION.AND;
  237. } else if ((Character) second == '|') {
  238. op = OPERATION.OR;
  239. } else {
  240. return null;
  241. }
  242. myStack.addFirst(new ConditionTree(op, first, third));
  243. } else {
  244. return null;
  245. }
  246. }
  247. }
  248. return new ConditionTree();
  249. }
  250. /**
  251. * Reads and returns a single term from the specified stack.
  252. *
  253. * @param stack The stack to be read
  254. * @return The ConditionTree representing the last element on the stack,
  255. * or null if it was not possible to create one.
  256. */
  257. private static ConditionTree readTerm(final Deque<Object> stack) {
  258. final Object first = stack.pollFirst();
  259. if (first instanceof Character && (Character) first == '!') {
  260. if (stack.isEmpty()) {
  261. return null;
  262. }
  263. return new ConditionTree(OPERATION.NOT, readTerm(stack));
  264. } else {
  265. if (!(first instanceof ConditionTree)) {
  266. return null;
  267. }
  268. return (ConditionTree) first;
  269. }
  270. }
  271. /**
  272. * Pops elements off of the end of the specified stack until an opening
  273. * bracket is reached, and then returns the parsed content of the bracket.
  274. *
  275. * @param stack The stack to be read for the bracket
  276. * @return The parsed contents of the bracket, or null if the brackets were
  277. * mismatched.
  278. */
  279. private static ConditionTree readBracket(final Deque<Object> stack) {
  280. final Deque<Object> tempStack = new ArrayDeque<Object>();
  281. boolean found = false;
  282. while (!found && !stack.isEmpty()) {
  283. final Object object = stack.pollLast();
  284. if (object instanceof Character && ((Character) object) == '(') {
  285. found = true;
  286. } else {
  287. tempStack.addFirst(object);
  288. }
  289. }
  290. if (found) {
  291. return parseStack(tempStack);
  292. } else {
  293. return null;
  294. }
  295. }
  296. /**
  297. * Determines if the specified character represents a single digit.
  298. *
  299. * @param target The character to be tested
  300. * @return True if the character is a digit, false otherwise
  301. */
  302. private static boolean isInt(final char target) {
  303. return target >= '0' && target <= '9';
  304. }
  305. /**
  306. * Creates a condition tree by disjoining the specified number of arguments
  307. * together.
  308. *
  309. * @param numArgs The number of arguments to be disjoined
  310. * @return The corresponding condition tree
  311. */
  312. public static ConditionTree createDisjunction(final int numArgs) {
  313. final StringBuilder builder = new StringBuilder();
  314. for (int i = 0; i < numArgs; i++) {
  315. if (builder.length() != 0) {
  316. builder.append('|');
  317. }
  318. builder.append(i);
  319. }
  320. return parseString(builder.toString());
  321. }
  322. /**
  323. * Creates a condition tree by conjoining the specified number of arguments
  324. * together.
  325. *
  326. * @param numArgs The number of arguments to be conjoined
  327. * @return The corresponding condition tree
  328. */
  329. public static ConditionTree createConjunction(final int numArgs) {
  330. final StringBuilder builder = new StringBuilder();
  331. for (int i = 0; i < numArgs; i++) {
  332. if (builder.length() != 0) {
  333. builder.append('&');
  334. }
  335. builder.append(i);
  336. }
  337. return parseString(builder.toString());
  338. }
  339. }