/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/ package org.apache.catalina.ssi;
import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.res.StringManager; /** * Represents a parsed expression. * * @author Paul Speed
*/ publicclass ExpressionParseTree { privatestaticfinal StringManager sm = StringManager.getManager(ExpressionParseTree.class); /** * Contains the current set of completed nodes. This is a workspace for the * parser. Needs to be LinkedList since it can contain {@code null}s.
*/ privatefinal LinkedList<Node> nodeStack = new LinkedList<>(); /** * Contains operator nodes that don't yet have values. This is a workspace * for the parser. Needs to be LinkedList since it can contain {@code null}s.
*/ privatefinal LinkedList<OppNode> oppStack = new LinkedList<>(); /** * The root node after the expression has been parsed.
*/ private Node root; /** * The SSIMediator to use when evaluating the expressions.
*/ privatefinal SSIMediator ssiMediator;
/** * Creates a new parse tree for the specified expression. * @param expr The expression string * @param ssiMediator Used to evaluated the expressions * @throws ParseException a parsing error occurred
*/ public ExpressionParseTree(String expr, SSIMediator ssiMediator) throws ParseException { this.ssiMediator = ssiMediator;
parseExpression(expr);
}
/** * Evaluates the tree and returns true or false. The specified SSIMediator * is used to resolve variable references. * * @return the evaluation result * * @throws SSIStopProcessingException If an error occurs evaluating the tree
*/ publicboolean evaluateTree() throws SSIStopProcessingException { try { return root.evaluate();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t); thrownew SSIStopProcessingException(t);
}
}
/** * Pushes a new operator onto the opp stack, resolving existing opps as * needed. * @param node The operator node
*/ privatevoid pushOpp(OppNode node) { // If node is null then it's just a group marker if (node == null) {
oppStack.add(0, node); return;
} while (true) { if (oppStack.size() == 0) { break;
}
OppNode top = oppStack.get(0); // If the top is a spacer then don't pop // anything if (top == null) { break;
} // If the top node has a lower precedence then // let it stay if (top.getPrecedence() < node.getPrecedence()) { break;
} // Remove the top node
oppStack.remove(0); // Let it fill its branches
top.popValues(nodeStack); // Stick it on the resolved node stack
nodeStack.add(0, top);
} // Add the new node to the opp stack
oppStack.add(0, node);
}
/** * Resolves all pending opp nodes on the stack until the next group marker * is reached.
*/ privatevoid resolveGroup() {
OppNode top = null; while ((top = oppStack.remove(0)) != null) { // Let it fill its branches
top.popValues(nodeStack); // Stick it on the resolved node stack
nodeStack.add(0, top);
}
}
/** * Parses the specified expression into a tree of parse nodes. * @param expr The expression to parse * @throws ParseException a parsing error occurred
*/ privatevoid parseExpression(String expr) throws ParseException {
StringNode currStringNode = null; // We cheat a little and start an artificial // group right away. It makes finishing easier.
pushOpp(null);
ExpressionTokenizer et = new ExpressionTokenizer(expr); while (et.hasMoreTokens()) { int token = et.nextToken(); if (token != ExpressionTokenizer.TOKEN_STRING) {
currStringNode = null;
} switch (token) { case ExpressionTokenizer.TOKEN_STRING : if (currStringNode == null) {
currStringNode = new StringNode(et.getTokenValue());
nodeStack.add(0, currStringNode);
} else { // Add to the existing
currStringNode.value.append(' ');
currStringNode.value.append(et.getTokenValue());
} break; case ExpressionTokenizer.TOKEN_AND :
pushOpp(new AndNode()); break; case ExpressionTokenizer.TOKEN_OR :
pushOpp(new OrNode()); break; case ExpressionTokenizer.TOKEN_NOT :
pushOpp(new NotNode()); break; case ExpressionTokenizer.TOKEN_EQ :
pushOpp(new EqualNode()); break; case ExpressionTokenizer.TOKEN_NOT_EQ :
pushOpp(new NotNode()); // Sneak the regular node in. The NOT will // be resolved when the next opp comes along.
oppStack.add(0, new EqualNode()); break; case ExpressionTokenizer.TOKEN_RBRACE : // Closeout the current group
resolveGroup(); break; case ExpressionTokenizer.TOKEN_LBRACE : // Push a group marker
pushOpp(null); break; case ExpressionTokenizer.TOKEN_GE :
pushOpp(new NotNode()); // Similar strategy to NOT_EQ above, except this // is NOT less than
oppStack.add(0, new LessThanNode()); break; case ExpressionTokenizer.TOKEN_LE :
pushOpp(new NotNode()); // Similar strategy to NOT_EQ above, except this // is NOT greater than
oppStack.add(0, new GreaterThanNode()); break; case ExpressionTokenizer.TOKEN_GT :
pushOpp(new GreaterThanNode()); break; case ExpressionTokenizer.TOKEN_LT :
pushOpp(new LessThanNode()); break; case ExpressionTokenizer.TOKEN_END : break;
}
} // Finish off the rest of the opps
resolveGroup(); if (nodeStack.size() == 0) { thrownew ParseException(sm.getString("expressionParseTree.noNodes"), et.getIndex());
} if (nodeStack.size() > 1) { thrownew ParseException(sm.getString("expressionParseTree.extraNodes"), et.getIndex());
} if (oppStack.size() != 0) { thrownew ParseException(sm.getString("expressionParseTree.unusedOpCodes"), et.getIndex());
}
root = nodeStack.get(0);
}
/** * A node in the expression parse tree.
*/ privateabstractstaticclass Node { /** * @return {@code true} if the node evaluates to true.
*/ publicabstractboolean evaluate();
} /** * A node the represents a String value
*/ privateclass StringNode extends Node {
StringBuilder value;
String resolved = null;
StringNode(String value) { this.value = new StringBuilder(value);
}
/** * Resolves any variable references and returns the value string. * * @return the value string
*/ public String getValue() { if (resolved == null) {
resolved = ssiMediator.substituteVariables(value.toString());
} return resolved;
}
/** * Returns true if the string is not empty.
*/
@Override publicboolean evaluate() { return !(getValue().length() == 0);
}
@Override public String toString() { return value.toString();
}
}
/** * A node implementation that represents an operation.
*/ privateabstractstaticclass OppNode extends Node { /** * The left branch.
*/
Node left; /** * The right branch.
*/
Node right;
/** * @return a precedence level suitable for comparison to other OppNode * preference levels.
*/ publicabstractint getPrecedence();
/** * Lets the node pop its own branch nodes off the front of the * specified list. The default pulls two. * * @param values The list from which to pop the values
*/ publicvoid popValues(List<Node> values) {
right = values.remove(0);
left = values.remove(0);
}
} privatestaticfinalclass NotNode extends OppNode {
@Override publicboolean evaluate() { return !left.evaluate();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.