package railo.runtime.type.util; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import railo.commons.digest.HashUtil; import railo.commons.lang.StringUtil; import railo.runtime.Page; import railo.runtime.PageContext; import railo.runtime.PagePlus; import railo.runtime.PageSource; import railo.runtime.dump.DumpData; import railo.runtime.dump.DumpProperties; import railo.runtime.dump.DumpRow; import railo.runtime.dump.DumpTable; import railo.runtime.dump.SimpleDumpData; import railo.runtime.exp.ApplicationException; import railo.runtime.exp.ExpressionException; import railo.runtime.exp.PageException; import railo.runtime.exp.PageExceptionImpl; import railo.runtime.op.Caster; import railo.runtime.op.Decision; import railo.runtime.type.Collection; import railo.runtime.type.Collection.Key; import railo.runtime.type.FunctionArgument; import railo.runtime.type.KeyImpl; import railo.runtime.type.Struct; import railo.runtime.type.UDF; import railo.runtime.type.UDFGSProperty; import railo.runtime.type.UDFPlus; import railo.runtime.type.scope.Argument; import railo.runtime.type.scope.ArgumentIntKey; import railo.transformer.library.function.FunctionLibFunction; import railo.transformer.library.function.FunctionLibFunctionArg; public class UDFUtil { private static final char CACHE_DEL = ';'; private static final char CACHE_DEL2 = ':'; private static final FunctionArgument[] EMPTY = new FunctionArgument[0]; /** * add detailed function documentation to the exception * @param pe * @param flf */ public static void addFunctionDoc(PageExceptionImpl pe,FunctionLibFunction flf) { ArrayList<FunctionLibFunctionArg> args=flf.getArg(); Iterator<FunctionLibFunctionArg> it = args.iterator(); // Pattern StringBuilder pattern=new StringBuilder(flf.getName()); StringBuilder end=new StringBuilder(); pattern.append("("); FunctionLibFunctionArg arg; int c=0; while(it.hasNext()){ arg = it.next(); if(!arg.isRequired()) { pattern.append(" ["); end.append("]"); } if(c++>0)pattern.append(", "); pattern.append(arg.getName()); pattern.append(":"); pattern.append(arg.getTypeAsString()); } pattern.append(end); pattern.append("):"); pattern.append(flf.getReturnTypeAsString()); pe.setAdditional(KeyConstants._Pattern, pattern); // Documentation StringBuilder doc=new StringBuilder(flf.getDescription()); StringBuilder req=new StringBuilder(); StringBuilder opt=new StringBuilder(); StringBuilder tmp; doc.append("\n"); it = args.iterator(); while(it.hasNext()){ arg = it.next(); tmp=arg.isRequired()?req:opt; tmp.append("- "); tmp.append(arg.getName()); tmp.append(" ("); tmp.append(arg.getTypeAsString()); tmp.append("): "); tmp.append(arg.getDescription()); tmp.append("\n"); } if(req.length()>0)doc.append("\nRequired:\n").append(req); if(opt.length()>0)doc.append("\nOptional:\n").append(opt); pe.setAdditional(KeyImpl.init("Documentation"), doc); } public static String callerHash(UDF udf, Object[] args, Struct values) throws ApplicationException { StringBuilder sb=new StringBuilder() .append(HashUtil.create64BitHash(udf.getPageSource().getDisplayPath())) .append(CACHE_DEL) .append(HashUtil.create64BitHash(udf.getFunctionName())) .append(CACHE_DEL); if(values!=null) { Iterator<Entry<Key, Object>> it = values.entryIterator(); Entry<Key, Object> e; while(it.hasNext()){ e = it.next(); if(!Decision.isSimpleValue(e.getValue())) throw new ApplicationException("only simple values are allowed as parameter for a function with cachedWithin"); sb.append(((KeyImpl)e.getKey()).hash()).append(CACHE_DEL2).append(HashUtil.create64BitHash(e.getValue().toString())).append(CACHE_DEL); } } else if(args!=null){ for(int i=0;i<args.length;i++){ if(!Decision.isSimpleValue(args[i])) throw new ApplicationException("only simple values are allowed as parameter for a function with cachedWithin"); sb.append(HashUtil.create64BitHash(args[i].toString())).append(CACHE_DEL); } } return HashUtil.create64BitHashAsString(sb, Character.MAX_RADIX); } public static Object getDefaultValue(PageContext pc, PageSource ps, int udfIndex, int index, Object defaultValue) throws PageException { Page p=ComponentUtil.getPage(pc,ps); if(p instanceof PagePlus) return ((PagePlus)p).udfDefaultValue(pc,udfIndex,index,defaultValue); Object rtn = p.udfDefaultValue(pc,udfIndex,index); if(rtn==null) return defaultValue;// in that case it can make no diff between null and not existing, but this only happens with data from old ra files return rtn; } public static Object getDefaultValue(PageContext pc, UDFPlus udf, int index, Object defaultValue) throws PageException { Page p=ComponentUtil.getPage(pc,udf.getPageSource()); if(p instanceof PagePlus) return ((PagePlus)p).udfDefaultValue(pc,udf.getIndex(),index,defaultValue); Object rtn = p.udfDefaultValue(pc,udf.getIndex(),index); if(rtn==null) return defaultValue;// in that case it can make no diff between null and not existing, but this only happens with data from old ra files return rtn; } public static void argumentCollection(Struct values) { argumentCollection(values,EMPTY); } public static void argumentCollection(Struct values, FunctionArgument[] funcArgs) { Object value=values.removeEL(KeyConstants._argumentCollection); if(value !=null) { value=Caster.unwrap(value,value); if(value instanceof Argument) { Argument argColl=(Argument) value; Iterator<Key> it = argColl.keyIterator(); Key k; int i=-1; while(it.hasNext()) { i++; k = it.next(); if(funcArgs.length>i && k instanceof ArgumentIntKey) { if(!values.containsKey(funcArgs[i].getName())) values.setEL(funcArgs[i].getName(),argColl.get(k,Argument.NULL)); else values.setEL(k,argColl.get(k,Argument.NULL)); } else if(!values.containsKey(k)){ values.setEL(k,argColl.get(k,Argument.NULL)); } } } else if(value instanceof Collection) { Collection argColl=(Collection) value; //Collection.Key[] keys = argColl.keys(); Iterator<Key> it = argColl.keyIterator(); Key k; while(it.hasNext()) { k = it.next(); if(!values.containsKey(k)){ values.setEL(k,argColl.get(k,Argument.NULL)); } } } else if(value instanceof Map) { Map map=(Map) value; Iterator it = map.entrySet().iterator(); Map.Entry entry; Key key; while(it.hasNext()) { entry=(Entry) it.next(); key = Caster.toKey(entry.getKey(),null); if(!values.containsKey(key)){ values.setEL(key,entry.getValue()); } } } else if(value instanceof java.util.List) { java.util.List list=(java.util.List) value; Iterator it = list.iterator(); Object v; int index=0; Key k; while(it.hasNext()) { v= it.next(); k=ArgumentIntKey.init(++index); if(!values.containsKey(k)){ values.setEL(k,v); } } } else { values.setEL(KeyConstants._argumentCollection,value); } } } public static String toReturnFormat(int returnFormat,String defaultValue) { if(UDF.RETURN_FORMAT_WDDX==returnFormat) return "wddx"; else if(UDF.RETURN_FORMAT_JSON==returnFormat) return "json"; else if(UDF.RETURN_FORMAT_PLAIN==returnFormat) return "plain"; else if(UDF.RETURN_FORMAT_SERIALIZE==returnFormat) return "cfml"; else if(UDFPlus.RETURN_FORMAT_JAVA==returnFormat) return "java"; // NO XML else if(UDFPlus.RETURN_FORMAT_XML==returnFormat) return "xml"; else return defaultValue; } public static boolean isValidReturnFormat(int returnFormat) { return toReturnFormat(returnFormat,null)!=null; } public static int toReturnFormat(String[] returnFormats, int defaultValue) { if(ArrayUtil.isEmpty(returnFormats)) return defaultValue; int rf; for(int i=0;i<returnFormats.length;i++){ rf=toReturnFormat(returnFormats[i].trim(), -1); if(rf!=-1) return rf; } return defaultValue; } public static int toReturnFormat(String returnFormat, int defaultValue) { if(StringUtil.isEmpty(returnFormat,true)) return defaultValue; returnFormat=returnFormat.trim().toLowerCase(); if("wddx".equals(returnFormat)) return UDF.RETURN_FORMAT_WDDX; else if("json".equals(returnFormat)) return UDF.RETURN_FORMAT_JSON; else if("plain".equals(returnFormat)) return UDF.RETURN_FORMAT_PLAIN; else if("text".equals(returnFormat)) return UDF.RETURN_FORMAT_PLAIN; else if("serialize".equals(returnFormat)) return UDF.RETURN_FORMAT_SERIALIZE; else if("cfml".equals(returnFormat)) return UDF.RETURN_FORMAT_SERIALIZE; else if("cfm".equals(returnFormat)) return UDF.RETURN_FORMAT_SERIALIZE; else if("xml".equals(returnFormat)) return UDF.RETURN_FORMAT_XML; else if("java".equals(returnFormat)) return UDFPlus.RETURN_FORMAT_JAVA; return defaultValue; } public static int toReturnFormat(String returnFormat) throws ExpressionException { int rf = toReturnFormat(returnFormat,-1); if(rf!=-1) return rf; throw new ExpressionException("invalid returnFormat definition ["+returnFormat+"], valid values are [wddx,plain,json,cfml]"); } public static String toReturnFormat(int returnFormat) throws ExpressionException { if(UDF.RETURN_FORMAT_WDDX==returnFormat) return "wddx"; else if(UDF.RETURN_FORMAT_JSON==returnFormat) return "json"; else if(UDF.RETURN_FORMAT_PLAIN==returnFormat) return "plain"; else if(UDF.RETURN_FORMAT_SERIALIZE==returnFormat) return "cfml"; else if(UDFPlus.RETURN_FORMAT_JAVA==returnFormat) return "java"; else throw new ExpressionException("invalid returnFormat definition, valid values are [wddx,plain,json,cfml]"); } public static DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp,UDF udf, boolean closure) { if(!dp.getShowUDFs()) return new SimpleDumpData(closure?"<Closure>":"<UDF>"); // arguments FunctionArgument[] args = udf.getFunctionArguments(); DumpTable atts = closure?new DumpTable("udf","#ff00ff","#ffccff","#000000"):new DumpTable("udf","#cc66ff","#ffccff","#000000"); atts.appendRow(new DumpRow(63,new DumpData[]{new SimpleDumpData("label"),new SimpleDumpData("name"),new SimpleDumpData("required"),new SimpleDumpData("type"),new SimpleDumpData("default"),new SimpleDumpData("hint")})); for(int i=0;i<args.length;i++) { FunctionArgument arg=args[i]; DumpData def; try { Object oa=null; try { oa = UDFUtil.getDefaultValue(pageContext, (UDFPlus)udf, i, null);//udf.getDefaultValue(pageContext,i,null); } catch (PageException e1) { } if(oa==null)oa="null"; def=new SimpleDumpData(Caster.toString(oa)); } catch (PageException e) { def=new SimpleDumpData(""); } atts.appendRow(new DumpRow(0,new DumpData[]{ new SimpleDumpData(arg.getDisplayName()), new SimpleDumpData(arg.getName().getString()), new SimpleDumpData(arg.isRequired()), new SimpleDumpData(arg.getTypeAsString()), def, new SimpleDumpData(arg.getHint())})); //atts.setRow(0,arg.getHint()); } DumpTable func = closure?new DumpTable("#ff00ff","#ffccff","#000000"):new DumpTable("#cc66ff","#ffccff","#000000"); if(closure) func.setTitle("Closure"); else { String f="Function "; try { f=StringUtil.ucFirst(ComponentUtil.toStringAccess(udf.getAccess()).toLowerCase())+" "+f; } catch (ExpressionException e) {} f+=udf.getFunctionName(); if(udf instanceof UDFGSProperty) f+=" (generated)"; func.setTitle(f); } if(udf instanceof UDFPlus)func.setComment("source:"+((UDFPlus)udf).getPageSource().getDisplayPath()); if(!StringUtil.isEmpty(udf.getDescription()))func.setComment(udf.getDescription()); func.appendRow(1,new SimpleDumpData("arguments"),atts); func.appendRow(1,new SimpleDumpData("return type"),new SimpleDumpData(udf.getReturnTypeAsString())); boolean hasLabel=!StringUtil.isEmpty(udf.getDisplayName());//displayName!=null && !displayName.equals(""); boolean hasHint=!StringUtil.isEmpty(udf.getHint());//hint!=null && !hint.equals(""); if(hasLabel || hasHint) { DumpTable box = new DumpTable("#ffffff","#cccccc","#000000"); box.setTitle(hasLabel?udf.getDisplayName():udf.getFunctionName()); if(hasHint)box.appendRow(0,new SimpleDumpData(udf.getHint())); box.appendRow(0,func); return box; } return func; } }