001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.type.util;
020
021import java.util.ArrayList;
022import java.util.Iterator;
023import java.util.Map;
024import java.util.Map.Entry;
025
026import lucee.commons.lang.ExceptionUtil;
027import lucee.commons.lang.StringUtil;
028import lucee.runtime.Page;
029import lucee.runtime.PageContext;
030import lucee.runtime.PagePlus;
031import lucee.runtime.PageSource;
032import lucee.runtime.dump.DumpData;
033import lucee.runtime.dump.DumpProperties;
034import lucee.runtime.dump.DumpRow;
035import lucee.runtime.dump.DumpTable;
036import lucee.runtime.dump.SimpleDumpData;
037import lucee.runtime.exp.ExpressionException;
038import lucee.runtime.exp.PageException;
039import lucee.runtime.exp.PageExceptionImpl;
040import lucee.runtime.op.Caster;
041import lucee.runtime.type.Collection;
042import lucee.runtime.type.Collection.Key;
043import lucee.runtime.type.FunctionArgument;
044import lucee.runtime.type.KeyImpl;
045import lucee.runtime.type.Struct;
046import lucee.runtime.type.UDF;
047import lucee.runtime.type.UDFGSProperty;
048import lucee.runtime.type.UDFPlus;
049import lucee.runtime.type.scope.Argument;
050import lucee.runtime.type.scope.ArgumentIntKey;
051import lucee.transformer.library.function.FunctionLibFunction;
052import lucee.transformer.library.function.FunctionLibFunctionArg;
053
054public class UDFUtil {
055
056        public static final short TYPE_UDF=1;
057        public static final short TYPE_BIF=2;
058        public static final short TYPE_CLOSURE=4;
059
060        private static final char CACHE_DEL = ';';
061        private static final char CACHE_DEL2 = ':';
062
063        private static final FunctionArgument[] EMPTY = new FunctionArgument[0];
064        
065        /**
066         * add detailed function documentation to the exception
067         * @param pe
068         * @param flf
069         */
070        public static void addFunctionDoc(PageExceptionImpl pe,FunctionLibFunction flf) {
071                ArrayList<FunctionLibFunctionArg> args=flf.getArg();
072                Iterator<FunctionLibFunctionArg> it = args.iterator();
073                
074                // Pattern
075                StringBuilder pattern=new StringBuilder(flf.getName());
076                StringBuilder end=new StringBuilder();
077                pattern.append("(");
078                FunctionLibFunctionArg arg;
079                int c=0;
080                while(it.hasNext()){
081                        arg = it.next();
082                        if(!arg.isRequired()) {
083                                pattern.append(" [");
084                                end.append("]");
085                        }
086                        if(c++>0)pattern.append(", ");
087                        pattern.append(arg.getName());
088                        pattern.append(":");
089                        pattern.append(arg.getTypeAsString());
090                        
091                }
092                pattern.append(end);
093                pattern.append("):");
094                pattern.append(flf.getReturnTypeAsString());
095                
096                pe.setAdditional(KeyConstants._Pattern, pattern);
097                
098                // Documentation
099                StringBuilder doc=new StringBuilder(flf.getDescription());
100                StringBuilder req=new StringBuilder();
101                StringBuilder opt=new StringBuilder();
102                StringBuilder tmp;
103                doc.append("\n");
104                
105                it = args.iterator();
106                while(it.hasNext()){
107                        arg = it.next();
108                        tmp=arg.isRequired()?req:opt;
109                        
110                        tmp.append("- ");
111                        tmp.append(arg.getName());
112                        tmp.append(" (");
113                        tmp.append(arg.getTypeAsString());
114                        tmp.append("): ");
115                        tmp.append(arg.getDescription());
116                        tmp.append("\n");
117                }
118
119                if(req.length()>0)doc.append("\nRequired:\n").append(req);
120                if(opt.length()>0)doc.append("\nOptional:\n").append(opt);
121                
122                
123                pe.setAdditional(KeyImpl.init("Documentation"), doc);
124                
125        }
126
127        public static Object getDefaultValue(PageContext pc, PageSource ps, int udfIndex, int index, Object defaultValue) throws PageException {
128                Page p=ComponentUtil.getPage(pc,ps);
129        if(p instanceof PagePlus) return ((PagePlus)p).udfDefaultValue(pc,udfIndex,index,defaultValue);
130        Object rtn = p.udfDefaultValue(pc,udfIndex,index);
131        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
132        return rtn;
133        }
134        
135
136        public static Object getDefaultValue(PageContext pc, UDFPlus udf, int index, Object defaultValue) throws PageException {
137                Page p=ComponentUtil.getPage(pc,udf.getPageSource());
138                if(p instanceof PagePlus) return ((PagePlus)p).udfDefaultValue(pc,udf.getIndex(),index,defaultValue);
139                Object rtn = p.udfDefaultValue(pc,udf.getIndex(),index);
140        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
141        return rtn;
142        }
143
144        public static void argumentCollection(Struct values) {
145                argumentCollection(values,EMPTY);
146        }
147
148        public static void argumentCollection(Struct values, FunctionArgument[] funcArgs) {
149                Object value=values.removeEL(KeyConstants._argumentCollection);
150                if(value !=null) {
151                        value=Caster.unwrap(value,value);
152                        
153                        if(value instanceof Argument) {
154                                Argument argColl=(Argument) value;
155                                Iterator<Key> it = argColl.keyIterator();
156                                Key k;
157                                int i=-1;
158                            while(it.hasNext()) {
159                                i++;
160                                k = it.next();
161                                if(funcArgs.length>i && k instanceof ArgumentIntKey) {
162                                if(!values.containsKey(funcArgs[i].getName()))
163                                        values.setEL(funcArgs[i].getName(),argColl.get(k,Argument.NULL));
164                                else 
165                                        values.setEL(k,argColl.get(k,Argument.NULL));
166                                }
167                        else if(!values.containsKey(k)){
168                                values.setEL(k,argColl.get(k,Argument.NULL));
169                        }
170                    }
171                    }
172                        else if(value instanceof Collection) {
173                        Collection argColl=(Collection) value;
174                            //Collection.Key[] keys = argColl.keys();
175                                Iterator<Key> it = argColl.keyIterator();
176                                Key k;
177                                while(it.hasNext()) {
178                                k = it.next();
179                                if(!values.containsKey(k)){
180                                values.setEL(k,argColl.get(k,Argument.NULL));
181                        }
182                    }
183                    }
184                        else if(value instanceof Map) {
185                                Map map=(Map) value;
186                            Iterator it = map.entrySet().iterator();
187                            Map.Entry entry;
188                            Key key;
189                            while(it.hasNext()) {
190                                entry=(Entry) it.next();
191                                key = Caster.toKey(entry.getKey(),null);
192                                if(!values.containsKey(key)){
193                                values.setEL(key,entry.getValue());
194                        }
195                    }
196                    }
197                        else if(value instanceof java.util.List) {
198                                java.util.List list=(java.util.List) value;
199                            Iterator it = list.iterator();
200                            Object v;
201                            int index=0;
202                            Key k;
203                            while(it.hasNext()) {
204                                v= it.next();
205                                k=ArgumentIntKey.init(++index);
206                                if(!values.containsKey(k)){
207                                values.setEL(k,v);
208                        }
209                    }
210                    }
211                    else {
212                        values.setEL(KeyConstants._argumentCollection,value);
213                    }
214                } 
215        }
216        
217        public static String toReturnFormat(int returnFormat,String defaultValue) {
218                if(UDF.RETURN_FORMAT_WDDX==returnFormat)                return "wddx";
219                else if(UDF.RETURN_FORMAT_JSON==returnFormat)   return "json";
220                else if(UDF.RETURN_FORMAT_PLAIN==returnFormat)  return "plain";
221                else if(UDF.RETURN_FORMAT_SERIALIZE==returnFormat)      return "cfml";
222                else if(UDFPlus.RETURN_FORMAT_JAVA==returnFormat)       return "java";
223                // NO XML else if(UDFPlus.RETURN_FORMAT_XML==returnFormat)      return "xml";
224                else return defaultValue;
225        }
226        
227
228        public static boolean isValidReturnFormat(int returnFormat) {
229                return toReturnFormat(returnFormat,null)!=null;
230        }
231        
232
233        public static int toReturnFormat(String[] returnFormats, int defaultValue) {
234                if(ArrayUtil.isEmpty(returnFormats)) return defaultValue;
235                int rf;
236                for(int i=0;i<returnFormats.length;i++){
237                        rf=toReturnFormat(returnFormats[i].trim(), -1);
238                        if(rf!=-1) return rf;
239                }
240                return defaultValue;
241        }
242        public static int toReturnFormat(String returnFormat, int defaultValue) {
243                if(StringUtil.isEmpty(returnFormat,true)) return defaultValue;
244                
245                returnFormat=returnFormat.trim().toLowerCase();
246                if("wddx".equals(returnFormat))                         return UDF.RETURN_FORMAT_WDDX;
247                else if("json".equals(returnFormat))            return UDF.RETURN_FORMAT_JSON;
248                else if("plain".equals(returnFormat))           return UDF.RETURN_FORMAT_PLAIN;
249                else if("text".equals(returnFormat))            return UDF.RETURN_FORMAT_PLAIN;
250                else if("serialize".equals(returnFormat))       return UDF.RETURN_FORMAT_SERIALIZE;
251                else if("cfml".equals(returnFormat))    return UDF.RETURN_FORMAT_SERIALIZE;
252                else if("cfm".equals(returnFormat))     return UDF.RETURN_FORMAT_SERIALIZE;
253                else if("xml".equals(returnFormat))     return UDF.RETURN_FORMAT_XML;
254                else if("java".equals(returnFormat))    return UDFPlus.RETURN_FORMAT_JAVA;
255                return defaultValue;
256        }
257        
258
259        public static int toReturnFormat(String returnFormat) throws ExpressionException {
260                int rf = toReturnFormat(returnFormat,-1);
261                if(rf!=-1) return rf;
262                throw new ExpressionException("invalid returnFormat definition ["+returnFormat+"], valid values are [wddx,plain,json,cfml]");
263        }
264
265        public static String toReturnFormat(int returnFormat) throws ExpressionException {
266                if(UDF.RETURN_FORMAT_WDDX==returnFormat)                return "wddx";
267                else if(UDF.RETURN_FORMAT_JSON==returnFormat)   return "json";
268                else if(UDF.RETURN_FORMAT_PLAIN==returnFormat)  return "plain";
269                else if(UDF.RETURN_FORMAT_SERIALIZE==returnFormat)      return "cfml";
270                else if(UDFPlus.RETURN_FORMAT_JAVA==returnFormat)       return "java";
271                else throw new ExpressionException("invalid returnFormat definition, valid values are [wddx,plain,json,cfml]");
272        }
273        
274
275        public static DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp,UDF udf, short type) {
276        
277                if(!dp.getShowUDFs()) {
278                        if(TYPE_UDF==type) return new SimpleDumpData("<UDF>");
279                        if(TYPE_BIF==type) return new SimpleDumpData("<BIF>");
280                        if(TYPE_CLOSURE==type) return new SimpleDumpData("<Closure>");
281                }
282                // arguments
283                FunctionArgument[] args = udf.getFunctionArguments();
284        
285        DumpTable dtArgs;
286        if(TYPE_UDF==type)                      dtArgs = new DumpTable("udf","#cc66ff","#ffccff","#000000");
287        else if(TYPE_CLOSURE==type) dtArgs = new DumpTable("udf","#ff00ff","#ffccff","#000000");
288        else                                            dtArgs = new DumpTable("udf","#ffff66","#ffffcc","#000000");
289
290                dtArgs.appendRow(new DumpRow(63, new DumpData[]{new SimpleDumpData("label"), new SimpleDumpData("name"), new SimpleDumpData("required"), new SimpleDumpData("type"), new SimpleDumpData("default"), new SimpleDumpData("hint")}));
291                for(int i=0;i<args.length;i++) {
292                        FunctionArgument arg=args[i];
293                        DumpData def;
294                        try {
295                                Object oa=null;
296                try {
297                    oa = UDFUtil.getDefaultValue(pageContext, (UDFPlus)udf, i, null);//udf.getDefaultValue(pageContext,i,null);
298                } catch (Throwable t) {
299                                ExceptionUtil.rethrowIfNecessary(t);
300                }
301                if(oa==null)oa="null";
302                                def=new SimpleDumpData(Caster.toString(oa));
303                        } catch (PageException e) {
304                                def=new SimpleDumpData("");
305                        }
306                        dtArgs.appendRow(new DumpRow(0, new DumpData[]{
307                    new SimpleDumpData(arg.getDisplayName()),
308                    new SimpleDumpData(arg.getName().getString()),
309                    new SimpleDumpData(arg.isRequired()),
310                    new SimpleDumpData(arg.getTypeAsString()),
311                    def,
312                    new SimpleDumpData(arg.getHint())}));
313                        //dtArgs.setRow(0,arg.getHint());
314                        
315                }
316                DumpTable func;
317                if(TYPE_CLOSURE==type) {
318                        func=new DumpTable("function", "#ff00ff", "#ffccff", "#000000");
319                        func.setTitle("Closure");
320                }
321                else if(TYPE_UDF==type) {
322                        func=new DumpTable("function", "#cc66ff", "#ffccff", "#000000");
323                        String f="Function ";
324                        try {
325                                f=StringUtil.ucFirst(ComponentUtil.toStringAccess(udf.getAccess()).toLowerCase())+" "+f;
326                        } 
327                        catch (ExpressionException e) {}
328                        f+=udf.getFunctionName();
329                        if(udf instanceof UDFGSProperty) f+=" (generated)";
330                        func.setTitle(f);
331                }
332                else {
333                        String f="Build in Function "+udf.getFunctionName();
334                        
335                        
336                        func=new DumpTable("#ffff66","#ffffcc","#000000");
337                        func.setTitle(f);
338                }
339
340                if(udf instanceof UDFPlus) {
341                        PageSource ps = ((UDFPlus)udf).getPageSource();
342                        if(ps!=null)
343                                func.setComment("source:"+ps.getDisplayPath());
344                }
345
346                if(!StringUtil.isEmpty(udf.getDescription()))func.setComment(udf.getDescription());
347                
348                func.appendRow(1,new SimpleDumpData("arguments"),dtArgs);
349                func.appendRow(1,new SimpleDumpData("return type"),new SimpleDumpData(udf.getReturnTypeAsString()));
350                
351                boolean hasLabel=!StringUtil.isEmpty(udf.getDisplayName());//displayName!=null && !displayName.equals("");
352                boolean hasHint=!StringUtil.isEmpty(udf.getHint());//hint!=null && !hint.equals("");
353                
354                if(hasLabel || hasHint) {
355                        DumpTable box = new DumpTable("#ffffff","#cccccc","#000000");
356                        box.setTitle(hasLabel?udf.getDisplayName():udf.getFunctionName());
357                        if(hasHint)box.appendRow(0,new SimpleDumpData(udf.getHint()));
358                        box.appendRow(0,func);
359                        return box;
360                }
361                return func;
362        }
363
364
365}