/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package photoSpreadParser.photoSpreadExpression.photoSpreadFunctions; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import photoSpread.PhotoSpread; import photoSpread.PhotoSpreadException; import photoSpread.PhotoSpreadException.FormulaArgumentsError; import photoSpread.PhotoSpreadException.FormulaError; import photoSpread.PhotoSpreadException.IllegalArgumentException; import photoSpreadObjects.PhotoSpreadDoubleObject; import photoSpreadObjects.PhotoSpreadObject; import photoSpreadObjects.PhotoSpreadStringObject; import photoSpreadParser.photoSpreadExpression.PhotoSpreadComputable; import photoSpreadParser.photoSpreadExpression.PhotoSpreadContainerExpression; import photoSpreadParser.photoSpreadExpression.PhotoSpreadDoubleConstant; import photoSpreadParser.photoSpreadExpression.PhotoSpreadFormulaExpression; import photoSpreadParser.photoSpreadExpression.PhotoSpreadStringConstant; import photoSpreadParser.photoSpreadNormalizedExpression.PhotoSpreadNormalizedExpression; import photoSpreadTable.PhotoSpreadCell; import photoSpreadUtilities.Const; import photoSpreadUtilities.Misc; import photoSpreadUtilities.TreeSetRandomSubsetIterable; import com.sun.xml.internal.ws.util.StringUtils; /** * * @author skandel. Massively modified by Andreas Paepcke (i.e. it's not Sean's fault!) */ public abstract class PhotoSpreadFunction extends PhotoSpreadFormulaExpression implements PhotoSpreadComputable { private enum FuncReturnTypes { DOUBLE, PHOTO_SPREAD_OBJECT } HashMap<String , FuncReturnTypes> _returnType = new HashMap<String , FuncReturnTypes>() { private static final long serialVersionUID = 1L; { put("Sum", FuncReturnTypes.DOUBLE); put("Avg", FuncReturnTypes.DOUBLE); put("Count", FuncReturnTypes.DOUBLE); put("Min", FuncReturnTypes.DOUBLE); put("Max", FuncReturnTypes.DOUBLE); put("Union", FuncReturnTypes.PHOTO_SPREAD_OBJECT); } }; protected static long _numOfTerms = 0; PhotoSpreadCell _cell = null; private String _functionName = Const.NULL_VALUE_STRING; protected ArrayList<PhotoSpreadFormulaExpression> _arguments = new ArrayList<PhotoSpreadFormulaExpression>(); AllArgEvalResults _allArgResults = new AllArgEvalResults(); private static Class<?> _photoSpreadFunctionClass = null; private static String _packageName; /**************************************************** * Constructors *****************************************************/ public PhotoSpreadFunction() { this("<uninitialized>"); } public PhotoSpreadFunction(String functionName) { super(); _functionName = functionName.toLowerCase(); // Need name of package where all functions reside: _packageName = this.getClass().getPackage().getName(); // Get a hold on the PhotoSpreadFunction class // object, just for speed later on: try { _photoSpreadFunctionClass = Class.forName(_packageName + ".PhotoSpreadFunction"); } catch (ClassNotFoundException e) { Misc.showErrorMsgAndStackTrace(e, ""); //e.printStackTrace(); } } /**************************************************** * Getters/Setters for PhotoSpreadFunction *****************************************************/ private void setName(String funcName) { _functionName = funcName; } protected ArrayList<PhotoSpreadFormulaExpression> getArguments() { return _arguments; } public String getFunctionName() { return _functionName; } public void setCell (PhotoSpreadCell cell) { _cell = cell; } /**************************************************** * Methods *****************************************************/ public String toString () { String argsStr = ""; for (PhotoSpreadFormulaExpression arg : _arguments) { if (arg != _arguments.get(0)) argsStr += ", "; argsStr += arg; } return "<PhotoSpreadFunction " + _functionName + "(" + argsStr + ")>"; } /** * Create a function object from the function name * @param _functionName the name of the function to be created */ public static PhotoSpreadFunction getInstance(String functionName, PhotoSpreadCell cell) { PhotoSpreadFunction func = null; PhotoSpread.trace("Creating PhotoSpreadFunction('" + functionName + "')"); // Capitalize the function name so that all function calls // in the language may use function names lower case: String funcNameCapped = StringUtils.capitalize(functionName); try { // Instantiate the proper PhotoSpreadFunction subclass. That // precise subclass is unknown at this time, but will be known // at runtime when this method is invoked. We do promise the // compiler that whatever the instantiated function will be, // it will be a subclass of PhotoSpreadFunction: String fullyQualifiedFuncName = "photoSpreadParser.photoSpreadExpression.photoSpreadFunctions." + funcNameCapped; Class<? extends PhotoSpreadFunction> functionClass = Class.forName(fullyQualifiedFuncName).asSubclass(PhotoSpreadFunction.class); func = (PhotoSpreadFunction) functionClass.newInstance(); func.setName(funcNameCapped); func.setCell(cell); } catch ( ClassNotFoundException ex ){ throw new RuntimeException( ex + " Function class '" + funcNameCapped + "' must be in class path"); } catch( InstantiationException ex ){ throw new RuntimeException( ex + " Function classes class must be concrete"); } catch( IllegalAccessException ex ){ throw new RuntimeException( ex + " Function class must have a no-arg constructor"); } return func; } /** * Each runtime (i.e. actual) argument to the function is represented by a * formula expression of the proper type: a PhotoSpreadStringConstant * object for a string argument, a PhotoSpreadContainerExpression * for an argument like A1. These argument objects are kept * in an ArrayList. * @param argument the argument to add to the function. */ public void addArgument(PhotoSpreadFormulaExpression argument){ _arguments.add(argument); PhotoSpread.trace("Adding arg " + argument + " to " + this); } /* * Every function must be able to <code>evaluate()</code> itself. * NOTE: <code>evaluate()</code> is different from <code>valueOf()</code>. * <code>Evaluate()</code> returns a set of PhotoSpreadObject instances that * wrap the raw Java results of applying the function to its arguments. * <code>ValueOf</code> returns the actual Java item. The <code>valueOf()</code> * for <code>sum()</code> therefore returns <code>Double</code>s, while * its <code>evaluate()</code> method returns PhotoSpreadDoubleConstant * instances. * * @see photoSpreadParser.photoSpreadExpression.PhotoSpreadFormulaExpression#evaluate(photoSpreadTable.PhotoSpreadCell) */ public abstract TreeSetRandomSubsetIterable<PhotoSpreadObject> evaluate(PhotoSpreadCell cell) throws PhotoSpreadException.FormulaError; /* I *believe* that normalization is not required for functions. * The actual arguments all get normalized individually.(?) * * @see photoSpreadParser.photoSpreadExpression.PhotoSpreadFormulaExpression#normalize(photoSpreadTable.PhotoSpreadCell) */ @Override public PhotoSpreadNormalizedExpression normalize(PhotoSpreadCell cell) { return new PhotoSpreadNormalizedExpression(cell); // throw new RuntimeException("Normalization of functions is unimplemented."); } /**************************************************** * Methods that Execute Functions *****************************************************/ /* * This method is the workhorse of function execution. It resolves * all runtime argument to ArrayLists of PhotoSpreadDoubleConstants, * which are wrapped Java Doubles. * * I don't like the dispatch procedure I introduced in this * method. There must be a cleaner way. The problem is this: * The arguments to a PhotoSpread function may be of any * PhotoSpread type. Example: =sum(3, A1, avg(3,7)). * The code in this method takes argument by argument and * turns it into a PhotoSpreadDoubleConstant object, i.e. * into a wrapped Java double. * * This process of evaluating each argument requires a different * handler method for each type. In the above example: a handler * for PhotoSpreadDoubleConstant, PhotoSpreadContainerExpression, * and PhotoSpreadFunction. At compile time these types are not * yet known. */ public AllArgEvalResults valueOfArgs() throws FormulaError { for (PhotoSpreadFormulaExpression arg : getArguments()) { try { // For each argument to the function, call method 'getTermValues()', // which returns an ArgEvalResult; essentially an array of // results values: // Determine the argument's class: Class<? extends PhotoSpreadFormulaExpression> argClass = arg.getClass(); // Is the argument a (nested) function call? In that // case the class would be the very specific function // class: Sum, Avg, etc.: if (_photoSpreadFunctionClass.isAssignableFrom(argClass)) // If yes, cast the function obj argument UP to // PhotoSpreadFunction, so that method dispatch // will properly call the proper getTermValue() // method below: _allArgResults.addAllOneArgResults(getParmValue((PhotoSpreadFunction) arg)); else { // Argument is of type PhotoSpreadDoubleConstant, // PhotoSpreadStringConstant, PhotoSpreadContainerExpression, // etc. (i.e. not a nested function call). // Because Java doesn't have built-in runtime polymorphism, // we dispatch here, using Java's reflection capabilities. // We thereby find the relevant getParmValue() method to call. // The funky parameter 'new Class[] {argClass}' creates an // array of Class objects on the fly. The getMethod() // method requires an array of all the desired method's arguments. Method relevantGetTermValuesMethod = this.getClass().getMethod("getParmValue", new Class[] {argClass}); // ... and invoke the found method with the argument: _allArgResults.addAllOneArgResults((ArgEvalResult<?>) relevantGetTermValuesMethod.invoke(this, new Object[] {arg})); } } catch (NoSuchMethodException e) { throw new PhotoSpreadException.FormulaArgumentsError( "In function " + _functionName + ": illegal argument: '" + arg + "'. Don't know how to handle this argument to '" + _functionName + "'."); } catch (IllegalAccessException e) { throw new PhotoSpreadException.FormulaArgumentsError( "In function " + _functionName + ": illegal argument: '" + arg + "'. Don't know how to handle this argument to '" + _functionName + "'."); } catch (InvocationTargetException e) { throw new PhotoSpreadException.FormulaArgumentsError( "In function " + _functionName + ": illegal argument: '" + arg + "'. " + ((e.getMessage() == null) ? "" : "Additional info: '")); } } // end For loop return _allArgResults; } /** * Compute an argument of type function call. For instance, compute * the 'avg' in the following <code>sum</code> function: =sum(2, 4, avg(6, 2)): * @param funcObj The function object that wraps the function call argument. * @return ArrayList of wrapped Doubles. In the example above the list * would only contain a single object. * @throws FormulaArgumentsError */ protected ArgEvalResult<?> getParmValue(PhotoSpreadFunction funcObj) throws FormulaError { String funcName = funcObj.getFunctionName(); FuncReturnTypes retType = _returnType.get(funcName); switch (_returnType.get(funcObj.getFunctionName())) { case DOUBLE: ArgEvalResult<PhotoSpreadDoubleObject> resFuncCall_Double = _allArgResults.<PhotoSpreadDoubleObject>newResultSet(); resFuncCall_Double.add((PhotoSpreadDoubleObject) funcObj.valueOf()); return resFuncCall_Double; // break; case PHOTO_SPREAD_OBJECT: ArgEvalResult<PhotoSpreadObject> resFuncCall_Obj = _allArgResults.<PhotoSpreadObject>newResultSet(); resFuncCall_Obj.add((PhotoSpreadObject) funcObj.valueOf()); return resFuncCall_Obj; // break; default: throw new PhotoSpreadException.FormulaArgumentsError( "In function " + _functionName + ": illegal argument: '" + funcObj.getFunctionName() + "'."); } } /** * String arguments are inappropriate for the functions * we have as of this writing. Our functions so far only * operate on numeric arguments. So we throw an error in * this method. If we add functions that take string args, * (like concat(str1, str2)), those functions need to override. * * @param strConstTerm * @return never returns nicely. * @throws IllegalArgumentException */ public ArgEvalResult<PhotoSpreadStringObject> getParmValue(PhotoSpreadStringConstant strConstTerm) { // Make a return structure to return PhotoSpreadStringObject instances: ArgEvalResult<PhotoSpreadStringObject> res = new ArgEvalResult<PhotoSpreadStringObject>(); // Extract a PhotoSpreadStringObject from the passed-in PhotoSpreadStringConstant // (We downcast from the getObject() return type of PhotoSpreadObject: PhotoSpreadStringObject constObj = (PhotoSpreadStringObject)strConstTerm.getObject(); res.add(constObj); return res; } /** * Object-valued arguments: we don't have them right now. * If we ever do, this method should just return them, wrapped * in a return wrapper. * @param obj * @return never returns nicely. * @throws IllegalArgumentException */ public ArgEvalResult<PhotoSpreadObject> getParmValue(PhotoSpreadObject obj) throws IllegalArgumentException { throw new PhotoSpreadException.IllegalArgumentException( "In function " + _functionName + "() cannot use an 'object' (" + obj + ") as parameter to a numeric function."); } /** * Resolving a container argument to a function: we retrieve * each element of the container, try to force it to a PhotoSpreadDoubleConstant, * and return it. If the conversion fails, we throw a runtime * formula error. * @param containerTerm The container expression that holds the * items. Ex: A1 * @return ArrayList of PhotoSpreadDoubleConstant that correspond * to all the elements in the container. * @throws IllegalArgumentException */ public ArgEvalResult<PhotoSpreadObject> getParmValue(PhotoSpreadContainerExpression containerTerm) throws IllegalArgumentException { // Evaluate the container expression to get a // set of PhotoSpreadObject instances. Then put // them all into a result construct: return new ArgEvalResult<PhotoSpreadObject>(containerTerm.evaluate(_cell)); } /** * Resolving a Double constant argument is simple. Just * package the passed-in argument into an ArrayList, and * we're done. * @param doubleConstTerm * @return */ public ArgEvalResult<PhotoSpreadDoubleObject> getParmValue(PhotoSpreadDoubleConstant doubleConstTerm) { // Make a return structure to return PhotoSpreadDoubleObject instances: ArgEvalResult<PhotoSpreadDoubleObject> res = new ArgEvalResult<PhotoSpreadDoubleObject>(); // Extract a PhotoSpreadDoubleObject from the passed-in PhotoSpreadDoubleConstant // (We downcast from the getObject() return type of PhotoSpreadObject: PhotoSpreadDoubleObject constObj = (PhotoSpreadDoubleObject)doubleConstTerm.getObject(); res.add(constObj); return res; // Yes, yes, the whole method could be written in one line: // return new ArgEvalResult<PhotoSpreadDoubleObject>((PhotoSpreadDoubleObject)doubleConstTerm.getObject()); // ... but would you want to wade through that statement??? } }