/* * Copyright (C) 2000 - 2010 TagServlet Ltd * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://www.openbluedragon.org/ */ package com.naryx.tagfusion.cfm.parser; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Vector; import com.naryx.tagfusion.cfm.engine.cfArgStructData; import com.naryx.tagfusion.cfm.engine.cfArrayData; import com.naryx.tagfusion.cfm.engine.cfCatchData; import com.naryx.tagfusion.cfm.engine.cfData; import com.naryx.tagfusion.cfm.engine.cfEngine; import com.naryx.tagfusion.cfm.engine.cfJavaArrayData; import com.naryx.tagfusion.cfm.engine.cfNullData; import com.naryx.tagfusion.cfm.engine.cfStructData; import com.naryx.tagfusion.cfm.engine.cfmRunTimeException; import com.naryx.tagfusion.cfm.parser.script.CFFunctionParameter; import com.naryx.tagfusion.cfm.parser.script.userDefinedFunction; import com.naryx.tagfusion.expression.compile.expressionEngine; import com.naryx.tagfusion.expression.function.functionBase; public class CFFunctionExpression extends CFExpression { private static final long serialVersionUID = 1L; private String name; private Vector<CFExpression> args; // Vector of CFExpression's private boolean isUDF = true; private boolean isParamExists; public CFFunctionExpression(CFIdentifier _name, Vector<CFExpression> _args) throws ParseException { super(_name.getToken()); name = _name.getName().toLowerCase(); args = _args; isParamExists = name.equals("parameterexists"); isUDF = !expressionEngine.isFunction(name); // if it's a predefined function, check the number of params is legal if (!isUDF && !(args instanceof ArgumentsVector) ) { try { com.naryx.tagfusion.expression.function.functionBase f = com.naryx.tagfusion.expression.compile.expressionEngine.getFunction(name); if (_args.size() < f.getMin()) throw new ParseException(_name.getToken(), "The function " + name + " requires at least " + f.getMin() + " argument(s)."); if (_args.size() > f.getMax()) throw new ParseException(_name.getToken(), "The function " + name + " requires at most " + f.getMax() + " argument(s)."); } catch (com.naryx.tagfusion.cfm.engine.cfmRunTimeException ee) {} // shouldn't happen } } public byte getType() { return CFExpression.FUNCTION; } public String getFunctionName() { return name; } public boolean isUDF() { return isUDF; } public boolean isEscapeSingleQuotes() { if ( isUDF ){ return false; }else{ functionBase function; try { function = (functionBase) expressionEngine.getFunction( getFunctionName() ); return function.isEscapeSingleQuotes(); } catch ( cfmRunTimeException e ) { // shouldn't occur since function name should definitely exist at this stage com.nary.Debug.println( "Unexpected exception in cfTag.isEscapeSingleQuotes: " + e.getMessage() ); return true; } } } public cfData Eval(CFContext context) throws cfmRunTimeException { setLineCol(context); // definitely an identifier - cf functionBase function = null; // is it a UDF? if (isUDF) { function = context.getUDF(name); } else { // is it a predefined function function = expressionEngine.getFunction(name); } if (function == null) { // Throw as type expression to match CFMX CFException exc = new CFException("No such function exists - " + name + ".", context); exc.getCatchData().setType(cfCatchData.TYPE_EXPRESSION); throw exc; } // evaluate all the arguments - have to send them in reverse order for the predefined functions List<cfData> argVals; if (isUDF) { return context._lastExpr = ((userDefinedFunction) function).execute(context.getSession(), processArguments(context, (userDefinedFunction) function), true); } else if (isParamExists) { argVals = new ArrayList<cfData>( args.size() ); for (int i = (args.size() - 1); i >= 0; i--) { cfData arg = args.get(i).Eval(context); if (arg.getDataType() != cfData.CFLDATA) { throw new CFException("Invalid argument to ParameterExists().", context); } argVals.add(arg); } } else { // If this function supports named parameters we can if ( function.supportNamedParams() ) { setLineCol(context); return context._lastExpr = function.execute(context.getSession(), processArguments(context, function) ); } else { argVals = new ArrayList<cfData>(args.size()); for (int i = (args.size() - 1); i >= 0; i--) { cfData arg = args.get(i).Eval(context); if (arg instanceof indirectReferenceData) { arg = ((indirectReferenceData) arg).Get(context); } else if (arg.getDataType() == cfData.CFLDATA) { arg = ((cfLData) arg).Get(context); } // copy by value if it's a simple type and not the first argument if (((i > 0) && cfData.isSimpleValue(arg)) || arg.isLoopIndex()) { arg = arg.duplicate(); } argVals.add(arg); } } } setLineCol(context); return context._lastExpr = function.execute(context.getSession(), argVals); } /** * For transforming named arguments into * * @param _context * @param _function * @return * @throws cfmRunTimeException */ private cfArgStructData processArguments(CFContext _context, functionBase _function) throws cfmRunTimeException { cfArgStructData argStruct = new cfArgStructData(); if (args instanceof ArgumentsVector) { // if named arguments argStruct.setNamedBasedMode(); ArgumentsVector argsVtr = (ArgumentsVector) args; Iterator<String> keys = argsVtr.keys().iterator(); String nextKey; CFExpression nextArgExpression; while (keys.hasNext()) { nextKey = keys.next(); nextArgExpression = argsVtr.getNamedArg(nextKey); cfData nextArgVal = evaluateArgument(_context, nextArgExpression, isUDF ); if (nextKey.equalsIgnoreCase("argumentcollection")) { if (nextArgVal.isStruct()) { cfStructData argCollection = (cfStructData) nextArgVal; Object[] argKeys = argCollection.keys(); for (int i = 0; i < argKeys.length; i++) { String key = (String) argKeys[i]; cfData nextArg = argCollection.getData(key); if ( nextArg.isLoopIndex() ){ nextArg = nextArg.duplicate(); } argStruct.setData(key, nextArg); } } else { throw new CFException("argumentCollection must be a struct", _context); } } else { argStruct.setData(nextKey, nextArgVal); } } //Make sure we have the right params if ( !isUDF ){ List<String> formalParams = _function.getFormals(); StringBuilder sb = new StringBuilder(32); sb.append("Function: "); sb.append( name ); sb.append("("); for ( int x=0; x < formalParams.size(); x++ ) sb.append( formalParams.get(x) + ", " ); if ( sb.length() > 0 ){ sb.deleteCharAt( sb.length()-1 ); sb.deleteCharAt( sb.length()-1 ); } sb.append( " ); Min=" + _function.getMin() ); sb.append( "; Max=" + _function.getMax() ); sb.append( "; Wrong number of arguments" ); if (argStruct.size() < _function.getMin() || argStruct.size() > _function.getMax()){ throw new CFException( sb.toString(), _context ); } } } else { // Create the arguments array - this is empty if there are fewer actuals than formals int nargs = args.size(); List<cfStructData> formals = _function.getFormalArguments(); int nfargs; // no of formal args if (formals != null) { nfargs = formals.size(); // Instantiate the formals for (int i = 0; i < nfargs; i++) { cfData arg = CFUndefinedValue.UNDEFINED; if (i < args.size()) { arg = evaluateArgument(_context, args.elementAt(i), isUDF ); // note index increment is down to cfArrayData api that indexes from 1 as opposed to 0. String argName = formals.get(i).getData("name").getString(); argStruct.setData(argName, arg); } } } else if ( _function instanceof userDefinedFunction ){ List<CFFunctionParameter> formalList = ( (userDefinedFunction) _function ).getUDFFormals(); nfargs = formalList.size(); // Instantiate the formals for (int i = 0; i < nfargs; i++) { cfData arg; if (i < args.size()) { arg = evaluateArgument(_context, args.get(i)); } else if ( formalList.get(i).isDefaulted() ){ arg = formalList.get(i).getDefaultValue(_context); } else { arg = CFUndefinedValue.UNDEFINED; } argStruct.setData(formalList.get(i).getName(), arg); } } else { List<String> formalList = _function.getFormals(); nfargs = formalList.size(); // Instantiate the formals for (int i = 0; i < nfargs; i++) { cfData arg; if (i < args.size()) { arg = evaluateArgument(_context, args.get(i), isUDF ); } else { arg = CFUndefinedValue.UNDEFINED; } argStruct.setData(formalList.get(i), arg); } } // Put the remainder into "arguments" for (int i = nfargs; i < nargs; i = i + 1) { // note index increment is down to cfArrayData api that indexes from 1 as opposed to 0. argStruct.setData(String.valueOf(i + 1), evaluateArgument(_context, args.elementAt(i), isUDF ) ); } } return argStruct; } // default (package) scope intentionally public static cfData evaluateArgument(CFContext _context, CFExpression _argExp) throws cfmRunTimeException { return evaluateArgument(_context, _argExp, true); } public static cfData evaluateArgument(CFContext _context, CFExpression _argExp, boolean cfcMethod) throws cfmRunTimeException { cfData arg = _argExp.Eval(_context); if (arg.getDataType() == cfData.CFLDATA) { arg = ((cfLData) arg).Get(_context); if (cfcMethod) { // if basic type or array then copy - queries and structs are passed by reference if (cfData.isSimpleValue(arg)) { arg = arg.duplicate(); } else if (arg.getDataType() == cfData.CFARRAYDATA && !(arg instanceof cfJavaArrayData) && !cfEngine.isStrictPassArrayByReference() ) { arg = ((cfArrayData) arg).copy(); } }else if ( arg.getDataType() == cfData.CFNUMBERDATA || arg.getDataType() == cfData.CFSTRINGDATA ){ arg.invalidateLoopIndex(); if ( arg.isLoopIndex() ){ arg = arg.duplicate(); } } } if (cfcMethod && (arg.getDataType() == cfData.CFNULLDATA) && ((cfNullData) arg).isJavaNull()) { arg = CFUndefinedValue.UNDEFINED; } return arg; } public String Decompile(int indent) { String s = name + "("; for (int i = 0; i < args.size(); i++) { s += args.elementAt(i).Decompile(indent); if (i < args.size() - 1) { s += ", "; } } s += ")"; return s; } }