PageRenderTime 42ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/java/org/andya/confluence/utils/parse/ExpressionParser.java

https://bitbucket.org/jwalton/metadata-confluence-plugin
Java | 184 lines | 89 code | 14 blank | 81 comment | 32 complexity | ec3d20fc71e12b249b8739953c4dc6a8 MD5 | raw file
  1. /*
  2. * Copyright (c) 2006, 2007 Andy Armstrong, Kelsey Grant and other contributors.
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * * Redistributions of source code must retain the above copyright notice,
  9. * this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above copyright notice,
  11. * this list of conditions and the following disclaimer in the documentation
  12. * and/or other materials provided with the distribution.
  13. * * The names of contributors may not
  14. * be used to endorse or promote products derived from this software without
  15. * specific prior written permission.
  16. *
  17. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  18. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  21. * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  22. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  24. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  26. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. */
  28. package org.andya.confluence.utils.parse;
  29. import com.atlassian.renderer.v2.macro.MacroException;
  30. import java.util.LinkedList;
  31. import java.util.StringTokenizer;
  32. /**
  33. * This parses an expression used to determine which pages to include
  34. * in a metadata report. The language used is as follows:
  35. *
  36. * <ul>
  37. * <li>&lt;OR&gt; ,
  38. * <li>&lt;AND&gt; +
  39. * <li>&lt;OPEN&gt; (
  40. * <li>&lt;CLOSE&gt; )
  41. * <li>&lt;LABEL&gt; String Literal
  42. * </ul>
  43. *
  44. * @author Kelsey Grant
  45. *
  46. */
  47. public class ExpressionParser {
  48. /** Token Type for '(' */
  49. private static final int TT_OPEN = 0;
  50. /** Token Type for ')' */
  51. private static final int TT_CLOSE = 1;
  52. /** Token Type for a literal */
  53. private static final int TT_LABEL = 2;
  54. /** Token Type for ',' */
  55. private static final int TT_OR = 3;
  56. /** Token Type for '+' */
  57. private static final int TT_AND = 4;
  58. /**
  59. * Parse the given expression into a tree structure representing
  60. * the abstract syntax. This can then be iterated by using an
  61. * ExpressionVisitor
  62. *
  63. * @param expression the expression to be parsed
  64. * @return an expression tree
  65. * @throws MacroException if the syntax was invalid
  66. */
  67. public Expression parse(String expression) throws MacroException {
  68. final LinkedList<Token> tokenStream = parseTokens(expression, new LinkedList<Token>());
  69. Expression exp = parseExpression(tokenStream);
  70. if (!tokenStream.isEmpty()) {
  71. throw new MacroException("Encountered extra tokens - '" + tokenStream.toString() + "'");
  72. }
  73. return exp;
  74. }
  75. /**
  76. * Parse out the given expression from a stream of tokens
  77. *
  78. * @param tokenStream the tokens
  79. * @return the parsed expression
  80. * @throws MacroException
  81. */
  82. Expression parseExpression(LinkedList<Token> tokenStream) throws MacroException {
  83. if(tokenStream.isEmpty()) {
  84. throw new MacroException("Expected '(' or label, was no expression!");
  85. }
  86. Token t = (Token)tokenStream.removeFirst();
  87. final Expression first;
  88. // either open parentheses, so recurse down, or a literal
  89. if(t.type == TT_OPEN) {
  90. first = parseExpression(tokenStream);
  91. if(tokenStream.isEmpty()) {
  92. throw new MacroException("Expected ')', was end of stream");
  93. }
  94. t = (Token)tokenStream.removeFirst();
  95. if(t.type != TT_CLOSE) {
  96. throw new MacroException("Expected ')', was '" + t.token + "'");
  97. }
  98. } else if (t.type == TT_LABEL) {
  99. first = new LabelExpression(t.token);
  100. } else {
  101. throw new MacroException("Expected '(' or label, was '" + t.token + "'");
  102. }
  103. if(tokenStream.isEmpty()) {
  104. return first;
  105. }
  106. // after a literal, we have an optional operator followed by expression
  107. t = (Token)tokenStream.getFirst(); //peek just now...
  108. if(t.type == TT_OR || t.type == TT_AND) {
  109. tokenStream.removeFirst(); // kill the or
  110. Expression right = parseExpression(tokenStream);
  111. if (t.type == TT_OR) {
  112. return new OrExpression(first, right);
  113. }
  114. return new AndExpression(first, right);
  115. }
  116. // we got something else. who knows?
  117. return first;
  118. }
  119. /**
  120. * Create the token stream from the expression. This is the lexing step
  121. * of the parse
  122. *
  123. * @param expression the expression to be tokenized
  124. * @param tokenList an existing list of tokens to be added to
  125. * @return the list of tokens with the tokens from expression added
  126. */
  127. LinkedList<Token> parseTokens(String expression, LinkedList<Token> tokenList) {
  128. expression = expression.trim();
  129. if(expression.length() == 0) {
  130. return tokenList;
  131. }
  132. final char ch = expression.charAt(0);
  133. final Token t;
  134. if(ch == '(') {
  135. t = new Token(TT_OPEN, "(");
  136. } else if (ch == ')') {
  137. t = new Token(TT_CLOSE, ")");
  138. } else if (ch == ',') {
  139. t = new Token(TT_OR, ",");
  140. } else if (ch == '+') {
  141. t = new Token(TT_AND, "+");
  142. } else {
  143. // it must be a label literal. Consume until we hit one of the specials.
  144. final String label = consume(expression);
  145. t = new Token(TT_LABEL, label);
  146. }
  147. tokenList.add(t);
  148. return parseTokens(expression.substring(t.token.length()), tokenList);
  149. }
  150. /**
  151. * Consumes the given expression until a known special is encountered
  152. *
  153. * @param expression the expression to be consumed
  154. * @return the literal that was consumed
  155. */
  156. private String consume(String expression) {
  157. return new StringTokenizer(expression, ",+()").nextToken();
  158. }
  159. private static final class Token {
  160. private final int type;
  161. private final String token;
  162. public Token(int type, String token) {
  163. this.type = type;
  164. this.token = token;
  165. }
  166. public String toString() {
  167. return token;
  168. }
  169. }
  170. }