/* * 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