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

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