/* * FormulaVariableStack.java * * Created on July 8, 2006, 2:33 PM * * Copyright 2006 Lee Lofgern and Accounting Enhancements Inc Licensed 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 com.accountingenhancements.formula; import com.accountingenhancements.common.SupportParameters; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; /** *An ordered list of FormulaVariables that represents a parsed Formula.
*The formula is broken down into components separated by operators. The resulting stack is then processed first moving right until operator a leading operator has a greater precedence than the previous operator. Then the values are resolved and the math is solved working back toward the beginning of the stack until either the beginning of the stack or an operator of qreater precedence is encountered. Then the stack is scanned forward again. *The stack should alternate between Value type FormulaVariables and Operator type FormulaVariables. * * @author Lee lofgren lofgren_opensource@accountingenhancements.com * @version 0.1009102006 */ public class FormulaVariableStack extends ArrayList implements Cloneable { /**The formula that was handed into this class. reParse() reprocesses this formula*/ protected String formula; /**This is the result when the stack resolved.*/ protected FormulaVariable result=null; /**If this stack has been resolved then this is the highest level variable used in the solution. This is used when resolving, to evaluate whether the solution should be recalculated.*/ protected int highestLevelInSolution=-1; protected int lowestScale=0; /** * Creates a new instance of FormulaVariableStack */ public FormulaVariableStack() { super(); formula=null; } /** * Creates a new instance of FormulaVariableStack *@param formula the formula string that needs to be solved. This will be handed to FormulaVariableStack.parseFormula(). */ public FormulaVariableStack(String formula) throws ParseException { super(); parseFormula(formula); } /** *@param formula the formula to be parsed. The old formula (if one exists) and old stack are cleared out. */ public void parseFormula(String formula) throws ParseException{ this.formula=formula; //If formula is surrounded by parentheses then remove these so we can process their contents. if (formula!=null&&formula.length()>1&&formula.charAt(0)=='('&&formula.charAt(formula.length()-1)==')'){ this.formula=formula.substring(1,formula.length()-1); } reparseFormula(); } /** *Reparse the formula that was last processed. */ public void reparseFormula()throws java.text.ParseException{ int index; int prevIndex; int subIndex; int formulaLen; char curChar; boolean slashed; boolean lastEntryWasAnOperator; int parenCount; int operator; StringBuilder buffer; FormulaVariable newVariable=null; if(this.isEmpty()==false)this.clear(); //Wipe out old stack. index=0;lastEntryWasAnOperator=true; //Operators and values should alternate and first entry should be a value so act as though last entry was an operator. if (formula!=null&&(formulaLen=formula.length())>0){ buffer = new StringBuilder(formulaLen); while(index0)buffer.delete(0,buffer.length()); index++; slashed=false; while(index='0'&&curChar<='9'||(lastEntryWasAnOperator&&(curChar=='+'||curChar=='-'))){ //Of if((curChar=formula.charAt(index))=='"') //If number, possibly with a leading positive/negative sign. If this isn't a number but a plus/minus sign was found then two operators in a row were found so we'll give an error. if(buffer.length()>0)buffer.delete(0,buffer.length()); if(curChar=='-'||curChar=='+'){buffer.append(curChar);index++;if(index=formulaLen||curChar<'0'||curChar>'9')throw new java.text.ParseException("Two operators were found in a row. Formula: "+formula+" at index "+index,index); while(index='0'&&curChar<='9'){buffer.append(curChar);index++;} if(index='0'&&curChar<='9'){buffer.append(curChar);index++;} } newVariable=new FormulaVariable("",buffer.toString(),0); this.add(newVariable); newVariable=null; lastEntryWasAnOperator=false; } else if (((curChar&0x5F)>='A'&&(curChar&0x5F)<='Z')||curChar=='('){ //A variable, parenthesis or a function, or word based operator such as OR or AND, has been found //If this is a variable, parenthesis, or function, then we are copying it into a FormulaVariable without processing the segment because we want to procrastinate it. If this is part of an OR test and the previous portion is true then there would not be a reason to solve this segment. if(buffer.length()>0)buffer.delete(0,buffer.length()); //Add (possible) function or variable name, or operator to buffer while(index='A'&&(curChar&0x5F)<='Z')||(curChar>='0'&&curChar<='9'))){ //Variables and functions can be made up of alpha-numeric characters. buffer.append(curChar); index++; } while(index0&&(operator=FormulaVariable.findOperatorPrecedence(buffer.toString()))>=0){ if (lastEntryWasAnOperator==true)throw new java.text.ParseException("There are two Operators in a row in this Formula: "+formula+" at index "+index,index); newVariable=new FormulaVariable("",operator,0,0); this.add(newVariable); newVariable=null; lastEntryWasAnOperator=true; } else { //If this isn't an operator then throw error if we were expecting an operator. if (lastEntryWasAnOperator==false)throw new java.text.ParseException("The formula is missing an operator. Formula: "+formula+" at index "+index,index); //If parenthesis then add this whole segment to the buffer. The buffer may or may not already contain a function name. //We are going to append to the buffer until we find the closing parenthesis. Quoted strings will be copied, as is, and parentheses inside them will be ignored. if(index0){ //Find ending parenthesis and add to buffer until found prevIndex=index; curChar=formula.charAt(index); //If opening quote then add to buffer until (and including) closing quote. We are doing this in an inner loop to avoid counting parenthesis inside a string if (curChar=='"'){ buffer.append('"'); index++;curChar=0x0; while(index0)buffer.delete(0,buffer.length()); while(index='0'&&curChar<='9'))==false&&((curChar&0x5F)>='A'&&(curChar&0x5F)<='Z')==false&&curChar!=' '&&curChar!='"'&&curChar!='('){ buffer.append(curChar); index++; } operator=FormulaVariable.findOperatorPrecedence(buffer.toString()); if (FormulaVariable.testIsOperator(operator)==false&&buffer.length()>1&&((curChar=buffer.charAt(buffer.length()-1))=='-'||curChar=='+'||curChar=='!'||curChar=='~')){ //If this is an operator but ends with + or - then maybe the + or - is the sign of an upcomming number and not part of the operator (ex: 12*-2) or if this is an operator mixed with a unary operator then the unary operator isn't part of this operator (ex: A*!B). index--; buffer.deleteCharAt(buffer.length()-1); operator=FormulaVariable.findOperatorPrecedence(buffer.toString()); } if (FormulaVariable.testIsOperator(operator)==false&&buffer.length()>1&&((curChar=buffer.charAt(buffer.length()-1))=='!'||curChar=='~')){ //Doing the same thing again with the Not operators because we could be looking at something like 12+!-2 where we are adding the Not of -2 to 12. index--; buffer.deleteCharAt(buffer.length()-1); operator=FormulaVariable.findOperatorPrecedence(buffer.toString()); } if(FormulaVariable.testIsOperator(operator)==false){ throw new java.text.ParseException("Unrecognized Symbol in Formula: "+formula+" at index "+(index-1),index); } else { if(FormulaVariable.testIsUnaryOperator(operator)){ if(lastEntryWasAnOperator==false)throw new java.text.ParseException("There is a unary operator following a value in formula: "+formula+" at index "+(index-1),index); newVariable=new FormulaVariable("",operator,0,0); this.add(newVariable); newVariable=null; //We are not changing lastEntryWasAnOperator value because this is a unary operator and doesn't count as the type of operator we are tracking You are allowed to have a unary operator follow a normal operator. } else { if (lastEntryWasAnOperator==true)throw new java.text.ParseException("There are two Operators in a row in this Formula: "+formula+" at index "+(index-1),index); newVariable=new FormulaVariable("",operator,0,0); this.add(newVariable); newVariable=null; lastEntryWasAnOperator=true; } } } while(index workStack; //Don't trash the real stack while solving; int variableType; //If only 1 entry then we don't need all of the stack processing below. Just solve it. It may be a function if(this.size()==1){ if (result==null||highestLevelInSolution>resolveEverythingAboveLevel){ variable=this.get(0); if(variable==null||variable.isNull())result=new FormulaVariable("","",FormulaVariable.TYPE_STRING,0,0); else{ result=variable.solve(variableList,0,supportParameters,functionList,resolveEverythingAboveLevel); highestLevelInSolution=result.getHighestLevel(); } } } else if (result==null||highestLevelInSolution>resolveEverythingAboveLevel) {//Don't need to process if the previously solved value didn't use any variables with a level higher than the resolveEverythingAboveLevel. result=null; //Copy stack into a work array so that we don't harm our own stack when we delete, already processed, sections stackPointer=0;workStack=new ArrayList();stackSize=this.size();while(stackPointer, , , or if(stackPointer0&&(stackPointer>=workStack.size()||workStack.get(stackPointer).isNonUnaryOperator()))throw new java.lang.ArithmeticException("Expected to find a value but ran into end of stack or found two operators in a row"); while(done==false){ if(workStack.size()==0)done=true; //If zero then problem of some sort occured and result will equal null else if(workStack.size()==1){ // If one then it must contain the final result or it is the only thing to return. result=workStack.get(0); done=true; } else if (stackPointer>=workStack.size()){ //Reached end //Don't assume that the prevPrecedence or curPrecedence are valid. Work backwards compiling a list of variables separated by the same operators. Ex: 1-6+5+B+4 <-- We want to group 6+5+B+4 together and replace the sequence with the result. endPointer=workStack.size()-1; if(workStack.get(endPointer).isOperator())throw new java.lang.ArithmeticException("Operator unexpected found at end of current stack: "+workStack.get(endPointer).toString()); stackPointer=endPointer-1; //Can't be less than zero of the previous [else if] would have been true if(workStack.get(stackPointer).isUnaryOperator())stackPointer--; if(stackPointer<0){ //We were at end of stack and backing up 1 moved us to beginning of stack so there is only two entries. A Unary Operator and a value. variable = FormulaVariableMath.solveUnaryOfVariable(workStack.get(stackPointer).getOperator(),workStack.get(stackPointer), variableList, supportParameters, functionList, resolveEverythingAboveLevel); if(variable!=null&&variable.getHighestLevel()>highestLevelInSolution)highestLevelInSolution=variable.getHighestLevel(); workStack.clear(); workStack.add(variable); } else { if(workStack.get(stackPointer).isNonUnaryOperator()==false)throw new java.lang.ArithmeticException("Missing an operator: "+workStack.get(stackPointer).toString()); curPrecedence=workStack.get(stackPointer).getOperator(); prevPrecedence=curPrecedence; while(prevPrecedence==curPrecedence){ stackPointer--; if(stackPointer<0)prevPrecedence=-1; if(workStack.get(stackPointer).isNonUnaryOperator())prevPrecedence=workStack.get(stackPointer).getOperator(); } //Either at beginning of stack or pointing to a different operator if (stackPointer<0)stackPointer=0; else if(workStack.get(stackPointer).isNonUnaryOperator())stackPointer++; startPointer=stackPointer; //Should now be pointing to first argument variable=solveRangeOfValuesSeparatedBySameOperator(workStack,startPointer,endPointer, variableList, supportParameters, functionList, resolveEverythingAboveLevel); if(variable!=null&&variable.getHighestLevel()>highestLevelInSolution)highestLevelInSolution=variable.getHighestLevel(); while(endPointer>=startPointer){workStack.remove(endPointer);endPointer--;}//endPointer was pointing at end of stack so we are deleting until end-of-stack. See [else if (stackPointer>=workStack.size())] above. workStack.add(variable); //Just deleted to end of stack, above, so append answer to end and point stack pointer at end so that we can loop around and do it again. We don't need to set the curPrecedence since we are still at end of stack. stackPointer=workStack.size()-1; } } else { // Of else if (stackPointer>=workStack.size()) //If here then prevPrecedence is valid and stackPointer is pointing just past the operator that generated the prevPrecedence //Let's find next operator and compare to previous. //If next one has a higher value then the value before this operator is the end of our processing //segment. Work our way to the start of segment that has the same operators. //ie: 5-4*5*6+7 we want 4*5*6 as our sequence. Then replace with solution so string //becomes 5-120+7 and prevPrecedence references - sign and stackPointer points to 120. //Find next operator that increases in precedence, meaning weaker such as going from * to +, or end of stack. curPrecedence=prevPrecedence; while(curPrecedence==prevPrecedence&&stackPointer+1=workStack.size())throw new java.lang.ArithmeticException("Expected to find a value but ran into end of stack"); if(workStack.get(stackPointer).isOperator())throw new java.lang.ArithmeticException("Expected to find a value but found two operators in a row"); if(stackPointer+1=curPrecedence){ prevPrecedence=curPrecedence; stackPointer+=2; // Advance to next variable following this precedence. } } } //The Current Value of curPrecedence from the above routine is no longer relevent. You can re-use this variable. //if(workStack.get(stackPointer).isNonUnaryOperator())throw new java.lang.ArithmeticException("Expected to find a value but found two operators in a row"); //if(workStack.get(stackPointer).isUnaryOperator())stackPointer++; if(stackPointer>=workStack.size())throw new java.lang.ArithmeticException("Expected to find a value but ran into end of stack"); endPointer=stackPointer;//Pointing to right most value while(stackPointer>=0&&(workStack.get(stackPointer).isNonUnaryOperator()==false||workStack.get(stackPointer).getOperator()==prevPrecedence))stackPointer--; //Should be pointing to beginning of stack or to an operator just before the first value, and just before the unary operator if one exists. if(stackPointer<0)stackPointer=0; else stackPointer++; startPointer=stackPointer; //Now pointing to first value, or the unary operator for the first value, of a run of entries that all use the same operator. variable=solveRangeOfValuesSeparatedBySameOperator(workStack,startPointer,endPointer, variableList, supportParameters, functionList, resolveEverythingAboveLevel); if(variable!=null&&variable.getHighestLevel()>highestLevelInSolution)highestLevelInSolution=variable.getHighestLevel(); while(endPointer>=startPointer){workStack.remove(endPointer);endPointer--;} //Remove original values and replace with new result workStack.add(startPointer,variable); //stackPointer is pointing to the variable that we just added. Lets get The Previous Operator if(stackPointer==0&&workStack.size()==1)prevPrecedence=-1; else if(stackPointer==0){ stackPointer++; if(workStack.get(stackPointer).isNonUnaryOperator()==false)throw new java.lang.ArithmeticException("Missing an operator: "+workStack.get(stackPointer).toString()); prevPrecedence=workStack.get(stackPointer).getOperator(); stackPointer++; } else { if(workStack.get(stackPointer-1).isNonUnaryOperator()==false)throw new java.lang.ArithmeticException("Missing an operator: "+workStack.get(stackPointer-1).toString()); prevPrecedence=workStack.get(stackPointer-1).getOperator(); } } //Of else Of else if (stackPointer>=workStack.size()) } // End while(done==false) } // Of if (result==null||highestLevelInSolution>resolveEverythingAboveLevel) return result; } //End public String solve( FormulaVariableList variableList, int iteration, HashMap supportParameters, FormulaFunctionList functionList,int resolveEverythingAboveLevel) public static FormulaVariable solveRangeOfValuesSeparatedBySameOperator(ArrayList stack, int startPointer, int endPointer, FormulaVariableList variableList, SupportParameters supportParameters, FormulaFunctionList functionList, int resolveEverythingAboveLevel)throws java.lang.ArithmeticException, ParseException, ClassNotFoundException{ //Range must be values separated by operators where all of the operators are the same FormulaVariable result=null; int index; double resultDouble; long resultLong; java.util.Date resultDate; String resultString; boolean resultBoolean; //StringBuilder buffer = new StringBuilder(); FormulaVariable leftOperand; FormulaVariable rightOperand; Integer optionalLeftUnaryOperator; Integer optionalRightUnaryOperator; int operator; boolean done; String operatorSymbol; if (startPointer>=0&&endPointer>=startPointer&&stack.size()>endPointer){ if(startPointer==endPointer)result=stack.get(startPointer); else { // Of if(startPointer==endPointer)result=stack.get(startPointer) index=startPointer; if(stack.get(index)==null)throw new java.lang.ArithmeticException("Error. null entry at position: "+index); while(index<=endPointer&&stack.get(index).isNonUnaryOperator()==false)index++; if(index>endPointer)throw new java.lang.ArithmeticException("Error Processing range from: "+startPointer +" To: "+endPointer+". Didn't find an operator"); operator=stack.get(index).getOperator(); operatorSymbol=stack.get(index).toString(); index=startPointer; done=false; //Get first result (Same as first argument) result=stack.get(index); index++; if(index>endPointer)throw new java.lang.ArithmeticException("Error Processing range from: "+startPointer +" To: "+endPointer+". Unexpectedly ran into end of range."); optionalLeftUnaryOperator=null; if(result.isUnaryOperator()){ optionalLeftUnaryOperator=result.getOperator(); result=stack.get(index); index++; if(index>endPointer)throw new java.lang.ArithmeticException("Error Processing range from: "+startPointer +" To: "+endPointer+". Unexpectedly ran into end of range."); } if(result==null||result.isNull())throw new java.lang.ArithmeticException("Error. null entry at position: "+(index-1)); //Start processing!!! while(index<=endPointer&&done==false){ if(stack.get(index).getVariableType()!=operator)throw new java.lang.ArithmeticException("Didn't find expected operator "+operatorSymbol+". Found <"+stack.get(index).toString()+"> instead at index: "+index); if(index>endPointer)throw new java.lang.ArithmeticException("Error Processing range from: "+startPointer +" To: "+endPointer+". Unexpectedly ran into end of range."); index++; leftOperand=result; rightOperand=stack.get(index); index++; optionalRightUnaryOperator=null; if(rightOperand!=null && rightOperand.isUnaryOperator()){ if(index>endPointer)throw new java.lang.ArithmeticException("Error Processing range from: "+startPointer +" To: "+endPointer+". Unexpectedly ran into end of range."); optionalRightUnaryOperator=rightOperand.getOperator(); rightOperand=stack.get(index); index++; } result=FormulaVariableMath.applyOperatorToTwoVariables(optionalLeftUnaryOperator,leftOperand,operator,optionalRightUnaryOperator,rightOperand,variableList,supportParameters,functionList,resolveEverythingAboveLevel); //If LOGICAL AND or LOGICAL OR then we can stop processing if false or true respectively. if((operator==FormulaVariable.TYPE_OPERATOR_AND&&result.getBoolean()==false)||(operator==FormulaVariable.TYPE_OPERATOR_AND&&result.getBoolean()==true))done=true; optionalLeftUnaryOperator=null; optionalRightUnaryOperator=null; } //End while(index<=endPointer&&done==false) } // Of else Of if(startPointer==endPointer)result=stack.get(startPointer) } // Of if (startPointer>=0&&endPointer>=startPointer&&stack.size()>endPointer) return result; } public FormulaVariable get(String formulaVariableName){ result=null; for(int i=0;result==null&&i *Deletes formulas whose levels are above the specified level and, in the existing formula's whose highestLevelInSolution is greater than the specified level, forces the variables to be re-solved during the next solve regardless of the solve parameter's resolveEverythingAboveLevel value. It is important to use this when a lower level value has changed and you wouldn't have run across these re-calculations during the lower level process. An example would be, while processing deduction codes, and employee number changed. The deduction code formulas are performed at a level 2 and the employee data is performed at a level 1. Once the level 1 stuff was processed, the level two routines wouldn't know that a level 1 value has changed and would continue to use old values. *@param specifiedLevel the level above which all results are to be purged. */ public void purgeResultsInStackGreaterThanSpecifiedLevel(int specifiedLevel){ if (specifiedLevelspecifiedLevel){remove(i);i--;} else get(i).purgeResultsGreaterThanSpecifiedLevel(specifiedLevel); } } }