All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.jexl2.Debugger Maven / Gradle / Ivy

There is a newer version: 2.1.1
Show newest version
/*
 * 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.commons.jexl2;

import java.util.regex.Pattern;
import org.apache.commons.jexl2.parser.ASTAdditiveNode;
import org.apache.commons.jexl2.parser.ASTAdditiveOperator;
import org.apache.commons.jexl2.parser.ASTAmbiguous;
import org.apache.commons.jexl2.parser.ASTAndNode;
import org.apache.commons.jexl2.parser.ASTArrayAccess;
import org.apache.commons.jexl2.parser.ASTArrayLiteral;
import org.apache.commons.jexl2.parser.ASTAssignment;
import org.apache.commons.jexl2.parser.ASTBitwiseAndNode;
import org.apache.commons.jexl2.parser.ASTBitwiseComplNode;
import org.apache.commons.jexl2.parser.ASTBitwiseOrNode;
import org.apache.commons.jexl2.parser.ASTBitwiseXorNode;
import org.apache.commons.jexl2.parser.ASTBlock;
import org.apache.commons.jexl2.parser.ASTConstructorNode;
import org.apache.commons.jexl2.parser.ASTDivNode;
import org.apache.commons.jexl2.parser.ASTEQNode;
import org.apache.commons.jexl2.parser.ASTERNode;
import org.apache.commons.jexl2.parser.ASTEmptyFunction;
import org.apache.commons.jexl2.parser.ASTFalseNode;
import org.apache.commons.jexl2.parser.ASTForeachStatement;
import org.apache.commons.jexl2.parser.ASTFunctionNode;
import org.apache.commons.jexl2.parser.ASTGENode;
import org.apache.commons.jexl2.parser.ASTGTNode;
import org.apache.commons.jexl2.parser.ASTIdentifier;
import org.apache.commons.jexl2.parser.ASTIfStatement;
import org.apache.commons.jexl2.parser.ASTJexlScript;
import org.apache.commons.jexl2.parser.ASTLENode;
import org.apache.commons.jexl2.parser.ASTLTNode;
import org.apache.commons.jexl2.parser.ASTMapEntry;
import org.apache.commons.jexl2.parser.ASTMapLiteral;
import org.apache.commons.jexl2.parser.ASTMethodNode;
import org.apache.commons.jexl2.parser.ASTModNode;
import org.apache.commons.jexl2.parser.ASTMulNode;
import org.apache.commons.jexl2.parser.ASTNENode;
import org.apache.commons.jexl2.parser.ASTNRNode;
import org.apache.commons.jexl2.parser.ASTNotNode;
import org.apache.commons.jexl2.parser.ASTNullLiteral;
import org.apache.commons.jexl2.parser.ASTNumberLiteral;
import org.apache.commons.jexl2.parser.ASTOrNode;
import org.apache.commons.jexl2.parser.ASTReference;
import org.apache.commons.jexl2.parser.ASTReferenceExpression;
import org.apache.commons.jexl2.parser.ASTReturnStatement;
import org.apache.commons.jexl2.parser.ASTSizeFunction;
import org.apache.commons.jexl2.parser.ASTSizeMethod;
import org.apache.commons.jexl2.parser.ASTStringLiteral;
import org.apache.commons.jexl2.parser.ASTTernaryNode;
import org.apache.commons.jexl2.parser.ASTTrueNode;
import org.apache.commons.jexl2.parser.ASTUnaryMinusNode;
import org.apache.commons.jexl2.parser.ASTVar;
import org.apache.commons.jexl2.parser.ASTWhileStatement;
import org.apache.commons.jexl2.parser.JexlNode;

import org.apache.commons.jexl2.parser.ParserVisitor;
import org.apache.commons.jexl2.parser.SimpleNode;

/**
 * Helps pinpoint the cause of problems in expressions that fail during evaluation.
 * 

* It rebuilds an expression string from the tree and the start/end offsets of the cause * in that string. *

* This implies that exceptions during evaluation do allways carry the node that's causing * the error. * @since 2.0 */ final class Debugger implements ParserVisitor { /** The builder to compose messages. */ private final StringBuilder builder; /** The cause of the issue to debug. */ private JexlNode cause; /** The starting character location offset of the cause in the builder. */ private int start; /** The ending character location offset of the cause in the builder. */ private int end; /** * Creates a Debugger. */ Debugger() { builder = new StringBuilder(); cause = null; start = 0; end = 0; } /** * Seeks the location of an error cause (a node) in an expression. * @param node the node to debug * @return true if the cause was located, false otherwise */ public boolean debug(JexlNode node) { start = 0; end = 0; if (node != null) { builder.setLength(0); this.cause = node; // make arg cause become the root cause JexlNode root = node; while (root.jjtGetParent() != null) { root = root.jjtGetParent(); } root.jjtAccept(this, null); } return end > 0; } /** * @return The rebuilt expression */ public String data() { return builder.toString(); } /** * Rebuilds an expression from a Jexl node. * @param node the node to rebuilt from * @return the rebuilt expression * @since 2.1 */ public String data(JexlNode node) { start = 0; end = 0; if (node != null) { builder.setLength(0); this.cause = node; node.jjtAccept(this, null); } return builder.toString(); } /** * @return The starting offset location of the cause in the expression */ public int start() { return start; } /** * @return The end offset location of the cause in the expression */ public int end() { return end; } /** * Checks if a child node is the cause to debug & adds its representation * to the rebuilt expression. * @param node the child node * @param data visitor pattern argument * @return visitor pattern value */ private Object accept(JexlNode node, Object data) { if (node == cause) { start = builder.length(); } Object value = node.jjtAccept(this, data); if (node == cause) { end = builder.length(); } return value; } /** * Adds a statement node to the rebuilt expression. * @param child the child node * @param data visitor pattern argument * @return visitor pattern value */ private Object acceptStatement(JexlNode child, Object data) { Object value = accept(child, data); // blocks, if, for & while dont need a ';' at end if (child instanceof ASTBlock || child instanceof ASTIfStatement || child instanceof ASTForeachStatement || child instanceof ASTWhileStatement) { return value; } builder.append(";"); return value; } /** * Checks if a terminal node is the the cause to debug & adds its * representation to the rebuilt expression. * @param node the child node * @param image the child node token image (may be null) * @param data visitor pattern argument * @return visitor pattern value */ private Object check(JexlNode node, String image, Object data) { if (node == cause) { start = builder.length(); } if (image != null) { builder.append(image); } else { builder.append(node.toString()); } if (node == cause) { end = builder.length(); } return data; } /** * Checks if the children of a node using infix notation is the cause to debug, * adds their representation to the rebuilt expression. * @param node the child node * @param infix the child node token * @param paren whether the child should be parenthesized * @param data visitor pattern argument * @return visitor pattern value */ private Object infixChildren(JexlNode node, String infix, boolean paren, Object data) { int num = node.jjtGetNumChildren(); //child.jjtGetNumChildren() > 1; if (paren) { builder.append("("); } for (int i = 0; i < num; ++i) { if (i > 0) { builder.append(infix); } accept(node.jjtGetChild(i), data); } if (paren) { builder.append(")"); } return data; } /** * Checks if the child of a node using prefix notation is the cause to debug, * adds their representation to the rebuilt expression. * @param node the node * @param prefix the node token * @param data visitor pattern argument * @return visitor pattern value */ private Object prefixChild(JexlNode node, String prefix, Object data) { boolean paren = node.jjtGetChild(0).jjtGetNumChildren() > 1; builder.append(prefix); if (paren) { builder.append("("); } accept(node.jjtGetChild(0), data); if (paren) { builder.append(")"); } return data; } /** {@inheritDoc} */ public Object visit(ASTAdditiveNode node, Object data) { // need parenthesis if not in operator precedence order boolean paren = node.jjtGetParent() instanceof ASTMulNode || node.jjtGetParent() instanceof ASTDivNode || node.jjtGetParent() instanceof ASTModNode; int num = node.jjtGetNumChildren(); //child.jjtGetNumChildren() > 1; if (paren) { builder.append("("); } accept(node.jjtGetChild(0), data); for (int i = 1; i < num; ++i) { accept(node.jjtGetChild(i), data); } if (paren) { builder.append(")"); } return data; } /** {@inheritDoc} */ public Object visit(ASTAdditiveOperator node, Object data) { builder.append(' '); builder.append(node.image); builder.append(' '); return data; } /** {@inheritDoc} */ public Object visit(ASTAndNode node, Object data) { return infixChildren(node, " && ", false, data); } /** {@inheritDoc} */ public Object visit(ASTArrayAccess node, Object data) { accept(node.jjtGetChild(0), data); int num = node.jjtGetNumChildren(); for (int i = 1; i < num; ++i) { builder.append("["); accept(node.jjtGetChild(i), data); builder.append("]"); } return data; } /** {@inheritDoc} */ public Object visit(ASTArrayLiteral node, Object data) { int num = node.jjtGetNumChildren(); builder.append("[ "); if (num > 0) { accept(node.jjtGetChild(0), data); for (int i = 1; i < num; ++i) { builder.append(", "); accept(node.jjtGetChild(i), data); } } builder.append(" ]"); return data; } /** {@inheritDoc} */ public Object visit(ASTAssignment node, Object data) { return infixChildren(node, " = ", false, data); } /** {@inheritDoc} */ public Object visit(ASTBitwiseAndNode node, Object data) { return infixChildren(node, " & ", false, data); } /** {@inheritDoc} */ public Object visit(ASTBitwiseComplNode node, Object data) { return prefixChild(node, "~", data); } /** {@inheritDoc} */ public Object visit(ASTBitwiseOrNode node, Object data) { boolean paren = node.jjtGetParent() instanceof ASTBitwiseAndNode; return infixChildren(node, " | ", paren, data); } /** {@inheritDoc} */ public Object visit(ASTBitwiseXorNode node, Object data) { boolean paren = node.jjtGetParent() instanceof ASTBitwiseAndNode; return infixChildren(node, " ^ ", paren, data); } /** {@inheritDoc} */ public Object visit(ASTBlock node, Object data) { builder.append("{ "); int num = node.jjtGetNumChildren(); for (int i = 0; i < num; ++i) { JexlNode child = node.jjtGetChild(i); acceptStatement(child, data); } builder.append(" }"); return data; } /** {@inheritDoc} */ public Object visit(ASTDivNode node, Object data) { return infixChildren(node, " / ", false, data); } /** {@inheritDoc} */ public Object visit(ASTEmptyFunction node, Object data) { builder.append("empty("); accept(node.jjtGetChild(0), data); builder.append(")"); return data; } /** {@inheritDoc} */ public Object visit(ASTEQNode node, Object data) { return infixChildren(node, " == ", false, data); } /** {@inheritDoc} */ public Object visit(ASTERNode node, Object data) { return infixChildren(node, " =~ ", false, data); } /** {@inheritDoc} */ public Object visit(ASTFalseNode node, Object data) { return check(node, "false", data); } /** {@inheritDoc} */ public Object visit(ASTForeachStatement node, Object data) { builder.append("for("); accept(node.jjtGetChild(0), data); builder.append(" : "); accept(node.jjtGetChild(1), data); builder.append(") "); if (node.jjtGetNumChildren() > 2) { acceptStatement(node.jjtGetChild(2), data); } else { builder.append(';'); } return data; } /** {@inheritDoc} */ public Object visit(ASTGENode node, Object data) { return infixChildren(node, " >= ", false, data); } /** {@inheritDoc} */ public Object visit(ASTGTNode node, Object data) { return infixChildren(node, " > ", false, data); } /** Checks identifiers that contain space, quote, double-quotes or backspace. */ private static final Pattern QUOTED_IDENTIFIER = Pattern.compile("['\"\\s\\\\]"); /** {@inheritDoc} */ public Object visit(ASTIdentifier node, Object data) { String image = node.image; if (QUOTED_IDENTIFIER.matcher(image).find()) { // quote it image = "'" + node.image.replace("'", "\\'") + "'"; } return check(node, image, data); } /** {@inheritDoc} */ public Object visit(ASTIfStatement node, Object data) { builder.append("if ("); accept(node.jjtGetChild(0), data); builder.append(") "); if (node.jjtGetNumChildren() > 1) { acceptStatement(node.jjtGetChild(1), data); if (node.jjtGetNumChildren() > 2) { builder.append(" else "); acceptStatement(node.jjtGetChild(2), data); } else { builder.append(';'); } } else { builder.append(';'); } return data; } /** {@inheritDoc} */ public Object visit(ASTNumberLiteral node, Object data) { return check(node, node.image, data); } /** {@inheritDoc} */ public Object visit(ASTJexlScript node, Object data) { int num = node.jjtGetNumChildren(); for (int i = 0; i < num; ++i) { JexlNode child = node.jjtGetChild(i); acceptStatement(child, data); } return data; } /** {@inheritDoc} */ public Object visit(ASTLENode node, Object data) { return infixChildren(node, " <= ", false, data); } /** {@inheritDoc} */ public Object visit(ASTLTNode node, Object data) { return infixChildren(node, " < ", false, data); } /** {@inheritDoc} */ public Object visit(ASTMapEntry node, Object data) { accept(node.jjtGetChild(0), data); builder.append(" : "); accept(node.jjtGetChild(1), data); return data; } /** {@inheritDoc} */ public Object visit(ASTMapLiteral node, Object data) { int num = node.jjtGetNumChildren(); builder.append("{ "); if (num > 0) { accept(node.jjtGetChild(0), data); for (int i = 1; i < num; ++i) { builder.append(", "); accept(node.jjtGetChild(i), data); } } else { builder.append(':'); } builder.append(" }"); return data; } /** {@inheritDoc} */ public Object visit(ASTConstructorNode node, Object data) { int num = node.jjtGetNumChildren(); builder.append("new "); builder.append("("); accept(node.jjtGetChild(0), data); for (int i = 1; i < num; ++i) { builder.append(", "); accept(node.jjtGetChild(i), data); } builder.append(")"); return data; } /** {@inheritDoc} */ public Object visit(ASTFunctionNode node, Object data) { int num = node.jjtGetNumChildren(); accept(node.jjtGetChild(0), data); builder.append(":"); accept(node.jjtGetChild(1), data); builder.append("("); for (int i = 2; i < num; ++i) { if (i > 2) { builder.append(", "); } accept(node.jjtGetChild(i), data); } builder.append(")"); return data; } /** {@inheritDoc} */ public Object visit(ASTMethodNode node, Object data) { int num = node.jjtGetNumChildren(); accept(node.jjtGetChild(0), data); builder.append("("); for (int i = 1; i < num; ++i) { if (i > 1) { builder.append(", "); } accept(node.jjtGetChild(i), data); } builder.append(")"); return data; } /** {@inheritDoc} */ public Object visit(ASTModNode node, Object data) { return infixChildren(node, " % ", false, data); } /** {@inheritDoc} */ public Object visit(ASTMulNode node, Object data) { return infixChildren(node, " * ", false, data); } /** {@inheritDoc} */ public Object visit(ASTNENode node, Object data) { return infixChildren(node, " != ", false, data); } /** {@inheritDoc} */ public Object visit(ASTNRNode node, Object data) { return infixChildren(node, " !~ ", false, data); } /** {@inheritDoc} */ public Object visit(ASTNotNode node, Object data) { builder.append("!"); accept(node.jjtGetChild(0), data); return data; } /** {@inheritDoc} */ public Object visit(ASTNullLiteral node, Object data) { check(node, "null", data); return data; } /** {@inheritDoc} */ public Object visit(ASTOrNode node, Object data) { // need parenthesis if not in operator precedence order boolean paren = node.jjtGetParent() instanceof ASTAndNode; return infixChildren(node, " || ", paren, data); } /** {@inheritDoc} */ public Object visit(ASTReference node, Object data) { int num = node.jjtGetNumChildren(); accept(node.jjtGetChild(0), data); for (int i = 1; i < num; ++i) { builder.append("."); accept(node.jjtGetChild(i), data); } return data; } /** {@inheritDoc} */ public Object visit(ASTReferenceExpression node, Object data) { JexlNode first = node.jjtGetChild(0); builder.append('('); accept(first, data); builder.append(')'); int num = node.jjtGetNumChildren(); for (int i = 1; i < num; ++i) { builder.append("["); accept(node.jjtGetChild(i), data); builder.append("]"); } return data; } /** {@inheritDoc} */ public Object visit(ASTReturnStatement node, Object data) { builder.append("return "); accept(node.jjtGetChild(0), data); return data; } /** {@inheritDoc} */ public Object visit(ASTSizeFunction node, Object data) { builder.append("size("); accept(node.jjtGetChild(0), data); builder.append(")"); return data; } /** {@inheritDoc} */ public Object visit(ASTSizeMethod node, Object data) { check(node, "size()", data); return data; } /** {@inheritDoc} */ public Object visit(ASTStringLiteral node, Object data) { String img = node.image.replace("'", "\\'"); return check(node, "'" + img + "'", data); } /** {@inheritDoc} */ public Object visit(ASTTernaryNode node, Object data) { accept(node.jjtGetChild(0), data); if (node.jjtGetNumChildren() > 2) { builder.append("? "); accept(node.jjtGetChild(1), data); builder.append(" : "); accept(node.jjtGetChild(2), data); } else { builder.append("?:"); accept(node.jjtGetChild(1), data); } return data; } /** {@inheritDoc} */ public Object visit(ASTTrueNode node, Object data) { check(node, "true", data); return data; } /** {@inheritDoc} */ public Object visit(ASTUnaryMinusNode node, Object data) { return prefixChild(node, "-", data); } /** {@inheritDoc} */ public Object visit(ASTVar node, Object data) { builder.append("var "); check(node, node.image, data); return data; } /** {@inheritDoc} */ public Object visit(ASTWhileStatement node, Object data) { builder.append("while ("); accept(node.jjtGetChild(0), data); builder.append(") "); if (node.jjtGetNumChildren() > 1) { acceptStatement(node.jjtGetChild(1), data); } else { builder.append(';'); } return data; } /** {@inheritDoc} */ public Object visit(SimpleNode node, Object data) { throw new UnsupportedOperationException("unexpected type of node"); } /** {@inheritDoc} */ public Object visit(ASTAmbiguous node, Object data) { throw new UnsupportedOperationException("unexpected type of node"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy