/*
* FormulaFunction.java
*
* Created on April 29, 2006, 9:21 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.*;
/**
* A template for defined function. This class isn't used directly, all formula functions should extend this class.
* For examples of how to define a function see FormulaFunction_Mid and FormulaFunction_IIF.
* The usual constructor used is the FormulaFunction(FormulaVariable functionVariable) where functionVariable is a FormulaVariable of type TYPE_FUNCTION.
* The (String) and (String, int) and (FormulaVariable) constructors use parseCommaDelimitedFunctionFields(String, int) to create the argument stack.
* Arguments are handed to these functions as the contents of a stack. The stack can be created through the use of the static parseCommaDelimitedFunctionFields method.
*
*The following is a convenient cut-and-paste for the base code for creating function code. Replace all code
* following your package statement with the following code. Perform a global replace on FormulaFunction_ with
* FormulaFunction_YourNewClassName then fix the FormulaFunction_YourNewClassNameYourNewClassName.java in the comment
* line at the top of this file.
*
*import com.accountingenhancements.common.SupportParameters;
*import com.accountingenhancements.formula.*;
*import java.text.ParseException;
*import java.util.HashMap;
*
*/**
**
**
**@author ????
**/
*
*public class FormulaFunction_ extends FormulaFunction{
*
*
* /**
* *Create a new FormulaFunction with an already defined functionArgumentStack.
* *@param functionArgumentStack a list of FormulaVariables that are needed by the function.
* */
* public FormulaFunction_(FormulaVariableStack functionArgumentStack){
* super(functionArgumentStack);
* }
*
* /**
* *Create a new FormulaFunction with a comma-delimited string representing the arguments needed by the function
* *@param functionArgumentString the string representing the functions arguments. The outer parentheses must be removed.
* *Example: If the function is IIF(A
* *@throws ParseException if arguments can't be parsed using parseCommaDelimitedFunctionFields(String,int)
* */
* public FormulaFunction_(String functionArgumentString) throws ParseException{
* super(functionArgumentString);
* }
*
* /**
* *Create a new FormulaFunction with a comma-delimited string representing the arguments needed by the function
* *@param functionArgumentString the string representing the functions arguments. The outer parentheses must be removed.
* *Example: If the function is IIF(A
* *@param level the level applied to the creation of FormulaVariables for the arguments.
* *@throws ParseException if arguments can't be parsed using parseCommaDelimitedFunctionFields(String,int)
* */
* public FormulaFunction_(String functionArgumentString, int level) throws ParseException{
* super(functionArgumentString,level);
* }
*
* /**
* *Create a new FormulaFunction from the functionVariable (A FormulaVariable of TYPE_FUNCTION).
* *This is the most common constructor used.
* *@param functionVariable a FormulaVariable of TYPE_FUNCTION
* *@throws ParseException if arguments can't be parsed using parseCommaDelimitedFunctionFields(String,int)
* */
* public FormulaFunction_(FormulaVariable functionVariable) throws ParseException{
* super(functionVariable);
* }
*
* /**
* *@return the name of this function
* */
* public static String getName(){
* return "";
* }
*
* /**
* *@return String[][] of needed arguments and their dataTypes which as {{"Test Argument ARG1","TYPE_BOOLEAN"},{"True Argument ARG2","TYPE_?"},{"False Argument ARG3","TYPE_?"}}
* *If handing this routine a FormulaVariableStack functionArgumentStack then the FormulaVariable arguments should be named, ARG1, ARG2, etc...
* */
* public static String[][] getRequiredArguments(){
* String[][] result={};
* return result;
* }
* /**
* *@return String[] description of return value and it's data type. {"Upper case result","TYPE_STRING"}
* */
* public static String[] getReturnValueDescription(){
* String[] result={};
* return result;
* }
* /**
* *Portion of code that actually solves the function.
* */
* protected FormulaVariable solve(FormulaVariableList variableList, int iteration, SupportParameters supportParameters, FormulaFunctionList functionList, int resolveEverythingAboveLevel) throws java.text.ParseException, java.lang.ArithmeticException, ClassNotFoundException{
* FormulaVariable solution=null;
* String resultString="";
* int highestLevelUsed=0;
* boolean treatUnquotedTextAsFormula=true;
* FormulaVariable Arg_1=null;
* Arg_1=functionArgumentStack.get("ARG1");
* if(Arg_1==null)throw new java.lang.ArithmeticException("ARG1 is missing from functionArgumentStack");
* Arg_1=Arg_1.solve(variableList,iteration,supportParameters,functionList,resolveEverythingAboveLevel);
* if(Arg_1==null||Arg_1.isNull()==true||Arg_1.getString().length()==0)throw new java.lang.ArithmeticException("ARG1 is invalid");
* if(highestLevelUsed < Arg_1.getHighestLevel())highestLevelUsed=Arg_1.getHighestLevel();
*
*
*
* solution=new FormulaVariable("",resultString,highestLevelUsed,treatUnquotedTextAsFormula);
* return solution;
* }
*
*}
*
*
*
* @author Lee lofgren lofgren_opensource@accountingenhancements.com
* @version 0.1009102006
*/
public class FormulaFunction {
/**The stack containing the function's arguments. It will also contain the result of this function once solved. and these functions should set thestack's highestLevelInSolution.*/
protected FormulaVariableStack functionArgumentStack=null;
/**
*Create a new FormulaFunction with an already defined functionArgumentStack.
*@param functionArgumentStack a list of FormulaVariables that are needed by the function.
*/
public FormulaFunction(FormulaVariableStack functionArgumentStack){
this.functionArgumentStack=functionArgumentStack;
}
/**
*Create a new FormulaFunction with a comma-delimited string representing the arguments needed by the function
*@param functionArgumentString the string representing the functions arguments. The outer parentheses must be removed.
*Example: If the function is IIF(A
*Example: If the function is IIF(A
*This is the most common constructor used.
*@param functionVariable a FormulaVariable of TYPE_FUNCTION
*@throws ParseException if arguments can't be parsed using parseCommaDelimitedFunctionFields(String,int)
*/
public FormulaFunction(FormulaVariable functionVariable) throws ParseException{
this.functionArgumentStack=getFunctionArgumentStackFromFunctionVariable(functionVariable);
}
/**
* Programmer defined function.
* This function replaces the funcionArgumentStack and calls function(FormulaVariableList variableList, int iteration, SupportParameters supportParameters, FormulaFunctionList functionList, int resolveEverythingAboveLevel)
* so you can leave this function alone, but the other function needs to be overriden.
* @param functionArgumentStack list of variables needed by function to return result.
* @param variableList the variable list used when solving for various variables.
* @param iteration used to prevent infinite loops.
* @param supportParameters a list of objects that this method may need to solve itself. One example is if this were an SQL function, one of the objects could be an sql connection.
* @param functionList list of functions that may be necessary when solving FormulaVariables on the functionArgumentStack.
* @param resolveEverythingAboveLevel reflects level at which TYPE_VARIABLE, TYPE_FUNCTION, and TYPE_FORMULA variables need to be re-solved.
* @return FormulaVariable containing the result
* @throws NoClassFoundException if solving the arguments requires access to an undefined function.
* @throws ArithmeticException, ParseException as handed through from FormulaVariableStack and if caused by various solutions in certain
* functions. SQL functions and the like will have to throw ArithmeticExceptions since we are faily generic here.
* If you add your own exceptions in your functions, note that it is up to you to support the exception if it fails when being
* solved in the FormulaVariableStack because it will be thrown as an unexpected exception. You are better off using the Arithmetic Exception
* in your defined function and setting the return message such that your external code can react appropriately to the error..
*/
public FormulaVariable function(FormulaVariableStack functionArgumentStack, FormulaVariableList variableList, int iteration, SupportParameters supportParameters, FormulaFunctionList functionList, int resolveEverythingAboveLevel) throws java.text.ParseException, java.lang.ArithmeticException, ClassNotFoundException {
this.functionArgumentStack=functionArgumentStack;
return function(variableList,iteration,supportParameters,functionList,resolveEverythingAboveLevel);
}
/**
* Programmer defined function.
*By default this method calls the solve(FormulaVariableList variableList, int iteration, SupportParameters supportParameters, FormulaFunctionList functionList, int resolveEverythingAboveLevel) throws java.text.ParseException, java.lang.ArithmeticException method.
* You must either override this method or override the following method.
* protected FormulaVariable solve(FormulaVariableList variableList, int iteration, SupportParameters supportParameters, FormulaFunctionList functionList, int resolveEverythingAboveLevel) throws java.text.ParseException, java.lang.ArithmeticException.
* If you want to override the help instructions then override this method, otherwise it is easier to override the solve method.
* @param variableList the variable list used when solving for various variables.
* @param iteration used to prevent infinite loops.
* @param supportParameters a list of objects that this method may need to solve itself. One example is if this were an SQL function, one of the objects could be an sql connection.
* @param functionList list of functions that may be necessary when solving FormulaVariables on the functionArgumentStack.
* @param resolveEverythingAboveLevel reflects level at which TYPE_VARIABLE, TYPE_FUNCTION, and TYPE_FORMULA variables need to be re-solved.
* @return FormulaVariable containing the result
* @throws ArithmeticException or ParseException as handed through from FormulaVariableStack and if caused by various solutions in certain
* functions. SQL functions and the like will have to throw ArithmeticExceptions since we are faily generic here.
* If you add your own exceptions in your functions, note that it is up to you to support the exception if it fails when being
* solved in the FormulaVariableStack because it will be thrown as an unexpected exception. You are better off using the Arithmetic Exception
* in your defined function and setting the return message such that your external code can react appropriately to the error..
*/
public FormulaVariable function(FormulaVariableList variableList, int iteration, SupportParameters supportParameters, FormulaFunctionList functionList, int resolveEverythingAboveLevel) throws java.text.ParseException, java.lang.ArithmeticException, ClassNotFoundException {
return solve(variableList, iteration, supportParameters, functionList, resolveEverythingAboveLevel);
}
/**
*@return the name of this function
*/
public static String getName(){
return "";
}
/**
*@return String[][] of needed classes supplied by SupportParameters in the form of {ParameterName, ParamaterClass} such as {{"Connection","java.sql.Connection"},{"Panel","javax.swing.JPanel"}}
*/
public static String[][] getRequiredSupportParameters(){
String[][] result={};
return result;
}
/**
*@return String[][] of needed FormulaVariables and their dataTypes such as {{"EmployeeName","TYPE_STRING"},{"BirthDate",TYPE_DATE"},{"CurrentDate","TYPE_DATE")}
*/
public static String[][] getRequiredFormulaVariables(){
String[][] result={};
return result;
}
/**
*@return String[][] of needed arguments and their dataTypes which as {{"Test Argument ARG1","TYPE_BOOLEAN"},{"True Argument ARG2","TYPE_?"},{"False Argument ARG3","TYPE_?"}}
*If handing this routine a FormulaVariableStack functionArgumentStack then the FormulaVariable arguments should be named, ARG1, ARG2, etc...
*/
public static String[][] getRequiredArguments(){
String[][] result={};
return result;
}
/**
*@return the current functionArgumentStack.
*/
public FormulaVariableStack getFunctionArgumentStack(){return functionArgumentStack;}
/**
*@param functionArgumentStack set the new functionArgumentStack. The functionArgumentStack is usually set once, then this class is created.
*/
public void setFunctionArgumentStack(FormulaVariableStack functionArgumentStack){this.functionArgumentStack=functionArgumentStack;}
/**
*@return String[] description of the return value as well as it's data type
*/
public static String[] getReturnValueDescription(){
String[] result={};
return result;
}
/**
* @param functionVariable create a functionArgumentStack from the data contained in a FormulaVariable of TYPE_FUNCTION.
* @throws ArithmeticException, ParseException
*/
protected static FormulaVariableStack getFunctionArgumentStackFromFunctionVariable(FormulaVariable functionVariable)throws java.lang.ArithmeticException, ParseException{
FormulaVariableStack result=null;
int index=0;
String function;
if (functionVariable==null)throw new java.lang.ArithmeticException("functionVariable was null");
if(functionVariable.getVariableType()!=FormulaVariable.TYPE_FUNCTION)throw new java.lang.ArithmeticException("The FormulaVariable was not of TYPE_FUNCTION");
if(functionVariable.isNull()||functionVariable.getString().length()==0)throw new java.lang.ArithmeticException("The FormulaVariable contained a null or empty value");
function=functionVariable.getString().trim();
index=function.indexOf('(');
if(index<0)throw new java.lang.ArithmeticException("FormulaVariable contents didn't contain an opening parenthesis");
if(function.charAt(function.length()-1)!=')')throw new java.lang.ArithmeticException("FormulaVariable contents didn't end with a closing parenthesis");
result=parseCommaDelimitedFunctionFields(function.substring(index+1,function.length()-1),functionVariable.getLevel());
return result;
}
/**
* Break the contents of a formula that are between parentheses into their constituent fields.
* Note: Examples are as seen in code, so "\\\"how\\\"" creates the string \"how\" and Hello\\, creates the string Hello\,
* Ex: "IIF(A
* formulaSegment = "A
* returnArray.get(0) == "A
* returnArray.get(1) == "\"Hello there, \\\"how\\\" are you\""
* returnArray.get(2) == "1.34"
* Escaping a comma will cause the comma to be ignored.
* Ex: "Hello\\, how are you,2"
* returnArray.get(0) == "Hello\\, how are you"
* returnArray.get(1) == "2"
* @param formulaSegment the contents between parentheses in a formula.
* @param level the level at which the variables in the newly created stack should be stamped.
* Usually level zero unless you have record specific information in the formulaSegment.
* Ideally, variables should be used when referencing record specific data so that the function
* only recalculates the result if the resolveEverythingAboveLevel value falls below the
* highestLevelInSolution recorded in this new stack.
* The variable names within the stack will be labeled ARG1, ARG2, ARG3, etc...
* @return a formulaVariableStack making up the comma separated fields in a function.
** @throws ArithmeticException or ParseException as handed through from FormulaVariableStack.
*/
protected static FormulaVariableStack parseCommaDelimitedFunctionFields(String formulaSegment, int level) throws java.text.ParseException, java.lang.ArithmeticException{
FormulaVariableStack result = new FormulaVariableStack();
int index;
int indexPrev;
int startOfSegment;
char curChar;
int size;
int slashCount;
int slashIndex;
int subIndex;
boolean done;
int argumentNumber=0;
if(formulaSegment!=null&&formulaSegment.length()!=0){
index=0;
startOfSegment=0;
size=formulaSegment.length();
while(index0&&formulaSegment.charAt(slashIndex)=='\\'){slashCount++;slashIndex--;}
if(slashCount%2==0){ //If it is escaped with a slash (mod not zero) then ignore else find end quote
subIndex=index+1;
done=false;
while(subIndex0&&formulaSegment.charAt(slashIndex)=='\\'){slashCount++;slashIndex--;}
if(slashCount%2==0)done=true;
else subIndex++;
} else subIndex++;
} //while(subIndex0&&formulaSegment.charAt(slashIndex)=='\\'){slashCount++;slashIndex--;}
if(slashCount%2==0){ //If it is escaped with a slash (mod not zero) then ignore else find end quote
argumentNumber++;
result.add(new FormulaVariable("ARG"+argumentNumber,formulaSegment.substring(startOfSegment,index),level,true));
index++;
startOfSegment=index;
} else index++;
} else index++;
if(index==indexPrev)index++; //prevent infinite loops.
}
if(startOfSegment