/***************************************************************************** SQLJEP - Java SQL Expression Parser 0.2 November 1 2006 (c) Copyright 2006, Alexey Gaidukov SQLJEP Author: Alexey Gaidukov SQLJEP is based on JEP 2.24 (http://www.singularsys.com/jep/) (c) Copyright 2002, Nathan Funk See LICENSE.txt for license information. *****************************************************************************/ package com.meidusa.amoeba.sqljep; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.meidusa.amoeba.sqljep.function.Comparative; import com.meidusa.amoeba.sqljep.function.ComparativeBaseList; import com.meidusa.amoeba.sqljep.function.Declare; import com.meidusa.amoeba.sqljep.function.PostfixCommand; import com.meidusa.amoeba.sqljep.function.PostfixCommandI; import com.meidusa.amoeba.sqljep.variable.Variable; import com.meidusa.amoeba.util.StaticString; import com.meidusa.amoeba.util.ThreadLocalMap; /** * Base class for different SQLJEP classes. This class doesn't know how to get source data. * <p/> * There are two types of variables in an expression. Columns of a abstarct table data and * external variables. Example of external variables are <CODE>TIMESTAMP,DATE,TIME,SYSDATE</CODE> variables. * <p/> * User should override methods {@link #findColumn(String name)} and {@link #getColumnObject(int column)} for defining * access to abstarct table data and the method {@link #getVariable(String name)} to access to external variables. * <p/> * To get a variable value at first {@link #getVariable(String name)} and after that {@link #findColumn(String name)} method is used. * <p/> * In expressions 'part_1.part2' names of variables are supported. * Parser is case sensitive. * <p/> * Visit <a href="http://sourceforge.net/projects/sqljep">http://sourceforge.net/projects/sqljep</a> * for the newest version of SQLJEP, and complete documentation. * @author Alexey Gaidukov */ public abstract class BaseJEP implements ParserVisitor { /** Debug flag for extra command line output */ public static final boolean debug = false; /** Parse time error List */ final protected List<String> errorList = new ArrayList<String>(); /** Node at the top of the parse tree */ protected Node[] nodes; protected String expression; /** * Creates a new SQLJEP instance. * Store String representation of the expression. <br/> * To compile it use {@link #parseExpression} method.<br/> * To evaluate use {@link #getValue} method. */ public BaseJEP(String exp) { if (exp == null) { throw new IllegalArgumentException("expression can be null"); } expression = exp; nodes = null; } /** * Change SQLJEP's state to initial state. * */ public void clear() { nodes = null; errorList.clear(); } /** * @return true if expresson is in compiled state otherwise false */ public boolean isValid() { return (nodes != null); } /** * Returns the top node of the expression tree. Because all nodes are * pointed to either directly or indirectly, the entire expression tree * can be accessed through this node. It may be used to manipulate the * expression, and subsequently evaluate it manually. * <p/> * Sometimes it is necessary to change value of a SQLJEP expression * by some external reasons. Then following code can be applied * <blockquote><pre> * Node topNode = sqljep.getTopNode(); * if (topNode instanceof ASTVarNode) { * ((ASTVarNode)topNode).variable.setValue(value); * } * else { * throw new org.medfoster.sqljep.ParseException("Expressions are not permitted"); * } * </pre></blockquote> * @return The top node of the expression tree */ public Node getTopNode() { return nodes[nodes.length-1]; } /** * method is used to get the value of the column in the current row of * abstract table data. * @return the column value * @param column Number in the abstract table data * @throws com.meidusa.amoeba.sqljep.ParseException */ @SuppressWarnings("unchecked") public abstract Comparable getColumnObject(int column) throws ParseException; /** /** * Returns true if an error occured during the most recent * action (parsing or evaluation). * @return Returns <code>true</code> if an error occured during the most * recent action (parsing or evaluation). */ boolean hasError() { return !errorList.isEmpty(); } /** * Reports information on the errors that occured during the most recent * action. * @return A string containing information on the errors, each separated * by a newline character; null if no error has occured */ String getErrorInfo() { if (hasError()) { String str = ""; for (String err : errorList) { str += err + "\n"; } return str; } else { return null; } } /** * Parses the expression. * The root of the expression tree is returned by {@link #getTopNode()} method * @throws com.meidusa.amoeba.sqljep.ParseException If there are errors in the expression then it fires the exception */ final public void parseExpression(Map<String,Integer> columnMapping,Map<String,Variable> variableMapping,final Map<String,PostfixCommand> funMap) throws ParseException { Reader reader = new StringReader(expression); Parser parser = new Parser(reader){ @Override public boolean containsKey(String key) { return funMap.containsKey(key); } @Override public PostfixCommandI getFunction(String name) { return funMap.get(name); } }; parser.setColumnMapping(columnMapping); parser.setVariableMapping(variableMapping); try { // try parsing nodes = parser.parseStream(reader); errorList.clear(); errorList.addAll(parser.getErrorList()); } catch (ParseException e) { // an exception was thrown, so there is no parse tree nodes = null; errorList.add(e.getMessage()); } catch (Throwable e) { throw new com.meidusa.amoeba.sqljep.ParseException(toString(), e); } if (hasError()) { throw new com.meidusa.amoeba.sqljep.ParseException(getErrorInfo()); } // If debug is enabled, print a dump of the tree to // standard output if (debug) { ParserVisitor v = new ParserDumpVisitor(); try { for(Node node : nodes){ node.jjtAccept(v, null); } } catch (ParseException e) { errorList.add(e.getMessage()); e.printStackTrace(); } } } /** * Returns the value of the expression as an object. The expression * tree is specified with its top node. The algorithm uses a stack * {@link com.meidusa.amoeba.sqljep.JepRuntime#stack} for evaluation. * <p> * An exception is thrown, if an error occurs during evaluation. * @return The value of the expression as an object. */ public Comparable<?> getValue(Comparable<?>[] row) throws ParseException { JepRuntime runtime = getThreadJepRuntime(this); runtime.row = row; for(Comparable<?> comparable :runtime.row){ if(comparable instanceof ComparativeBaseList){ runtime.isMultValue = true; } } try{ if (!isValid()) { throw new ParseException("Parser is not prepared"); } if (!hasError()) { runtime.stack.setSize(0); // evaluate by letting the top node accept the visitor for(Node node : nodes){ node.jjtAccept(this, null); } // something is wrong if not exactly one item remains on the stack // or if the error flag has been set if (runtime.stack.size() != 1) { throw new ParseException("Wrong stack state. Stack size: "+runtime.stack.size()); } // return the value of the expression return runtime.stack.pop(); } else { throw new ParseException(getErrorInfo()); } }finally{ runtime.vars.clear(); runtime.stack.setSize(0); runtime.isMultValue = false; } } /** * Compares two objects for equality. * Two SQLJEPs are equals only when their String representations are equals. */ public boolean equals(Object obj) { if (obj instanceof BaseJEP) { BaseJEP f = (BaseJEP)obj; return expression.equals(f.expression); } return false; } /** * Returns the expression string representation {@link #BaseJEP(String exp)} */ public String toString() { return expression; } /* ParserVisitor interface */ /** * This method should never be called when evaluation a normal * expression. */ final public Object visit(SimpleNode node, Object data) throws ParseException { return null; } /** * This method should never be called when evaluating a normal * expression. */ final public Object visit(ASTStart node, Object data) throws ParseException { return null; } /** * Visit a function node. The values of the child nodes * are first pushed onto the stack. Then the function class associated * with the node is used to evaluate the function. */ final public Object visit(ASTFunNode node, Object data) throws ParseException { JepRuntime runtime = getThreadJepRuntime(this); PostfixCommandI pfmc = node.getPFMC(); // check if the function class is set if (pfmc == null) { throw new ParseException("No function class associated with " + node.getName()); } // evaluate all children (each leaves their result on the stack) if (debug) { System.out.println("Stack size after childrenAccept: " + runtime.stack.size()); } Comparable<?>[] parameters = pfmc.evaluate(node, runtime); if(pfmc.isAutoBox()){ ComparativeBaseList list = null; int index = -1; for(int i=0;i<parameters.length;i++){ if(parameters[i] instanceof ComparativeBaseList){ index = i; list = (ComparativeBaseList)parameters[i]; break; }else{ } } if(index >=0){ for(int i=0;i<parameters.length;i++){ if(i != index){ if(parameters[i] instanceof Comparative){ parameters[i] =((Comparative) parameters[i]).getValue(); } } } for(Comparative comp:list.getList()){ parameters[index] = comp.getValue(); Comparable<?> value = pfmc.getResult(parameters); if(value instanceof Comparative){ comp.setComparison(((Comparative) value).getComparison()); comp.setValue(((Comparative) value).getValue()); }else{ comp.setValue(value); } } if(pfmc instanceof Declare){ Declare declare = (Declare) pfmc; declare.declare(runtime, list); }else{ runtime.stack.push(list); } }else{ //����ÿ�������Ƿ��� Comparative ���� Comparative lastComparative = null; for(int i=0;i<parameters.length;i++){ if(parameters[i] instanceof Comparative){ lastComparative = ((Comparative) parameters[i]); parameters[i] =((Comparative) parameters[i]).getValue(); } } Comparable<?> result = pfmc.getResult(parameters); if(lastComparative != null){ lastComparative.setValue(result); result = lastComparative; } if(pfmc instanceof Declare){ Declare declare = (Declare) pfmc; declare.declare(runtime, result); }else{ runtime.stack.push(result); } } }else{ if(pfmc instanceof Declare){ Declare declare = (Declare) pfmc; declare.declare(runtime, pfmc.getResult(parameters)); }else{ runtime.stack.push(pfmc.getResult(parameters)); } } if (debug) { System.out.println("Stack size after run: " + runtime.stack.size()); } return null; } /** * Visit a variable node. The value of the variable is obtained from the * model and pushed onto the stack. */ @SuppressWarnings("unchecked") final public Object visit(ASTVarNode node, Object data) throws ParseException { JepRuntime runtime = getThreadJepRuntime(this); if (node.index >= 0) { Comparable value = getColumnObject(node.index); if(value instanceof Comparative){ value = (Comparable)((Comparative)value).clone(); } runtime.stack.push(value); } else { if(node.variable == null){ Comparable comparable = runtime.vars.get(node.ident); runtime.stack.push(comparable); }else{ runtime.stack.push(node.variable.getValue()); } } return null; } /** * Visit a constant node. The value of the constant is pushed onto the * stack. */ final public Object visit(ASTConstant node, Object data) throws ParseException { JepRuntime runtime = getThreadJepRuntime(this); runtime.stack.push((Comparable<?>)node.value); return null; } final public Object visit(ASTArray node, Object data) throws ParseException { node.childrenAccept(this, null); return null; } public static JepRuntime getThreadJepRuntime(BaseJEP baseJep){ JepRuntime runtime = (JepRuntime)ThreadLocalMap.get(StaticString.JEP_RUNTIME); if(runtime == null){ runtime = new JepRuntime(baseJep); ThreadLocalMap.put(StaticString.JEP_RUNTIME, runtime); }else{ runtime.ev = baseJep; } return runtime; } }