001    package railo.runtime.type;
002    
003    import java.io.Externalizable;
004    import java.io.IOException;
005    import java.io.ObjectInput;
006    import java.io.ObjectOutput;
007    import java.util.Iterator;
008    import java.util.Map;
009    import java.util.Map.Entry;
010    
011    import javax.servlet.jsp.tagext.BodyContent;
012    
013    import railo.print;
014    import railo.commons.io.cache.Cache;
015    import railo.commons.lang.CFTypes;
016    import railo.commons.lang.SizeOf;
017    import railo.commons.lang.StringUtil;
018    import railo.runtime.Component;
019    import railo.runtime.ComponentImpl;
020    import railo.runtime.Page;
021    import railo.runtime.PageContext;
022    import railo.runtime.PageContextImpl;
023    import railo.runtime.PagePlus;
024    import railo.runtime.PageSource;
025    import railo.runtime.cache.ram.RamCache;
026    import railo.runtime.component.MemberSupport;
027    import railo.runtime.config.ConfigImpl;
028    import railo.runtime.config.NullSupportHelper;
029    import railo.runtime.dump.DumpData;
030    import railo.runtime.dump.DumpProperties;
031    import railo.runtime.dump.DumpRow;
032    import railo.runtime.dump.DumpTable;
033    import railo.runtime.dump.SimpleDumpData;
034    import railo.runtime.exp.ExpressionException;
035    import railo.runtime.exp.PageException;
036    import railo.runtime.exp.UDFCasterException;
037    import railo.runtime.functions.cache.Util;
038    import railo.runtime.listener.ApplicationContextSupport;
039    import railo.runtime.op.Caster;
040    import railo.runtime.op.Decision;
041    import railo.runtime.op.Duplicator;
042    import railo.runtime.tag.util.DeprecatedUtil;
043    import railo.runtime.type.Collection.Key;
044    import railo.runtime.type.scope.Argument;
045    import railo.runtime.type.scope.ArgumentIntKey;
046    import railo.runtime.type.scope.Local;
047    import railo.runtime.type.scope.LocalImpl;
048    import railo.runtime.type.scope.Undefined;
049    import railo.runtime.type.udf.UDFCacheEntry;
050    import railo.runtime.type.util.ComponentUtil;
051    import railo.runtime.type.util.KeyConstants;
052    import railo.runtime.type.util.UDFUtil;
053    import railo.runtime.writer.BodyContentUtil;
054    
055    /**
056     * defines a abstract class for a User defined Functions
057     */
058    public class UDFImpl extends MemberSupport implements UDFPlus,Sizeable,Externalizable {
059            
060            private static final FunctionArgument[] EMPTY = new FunctionArgument[0];
061            private static final RamCache DEFAULT_CACHE=new RamCache();
062            
063            
064            
065            protected ComponentImpl ownerComponent;
066            protected UDFPropertiesImpl properties;
067        
068            /**
069             * DO NOT USE THIS CONSTRUCTOR!
070             * this constructor is only for deserialize process
071             */
072            public UDFImpl(){
073                    super(0);
074            }
075            
076            public UDFImpl(UDFProperties properties) {
077                    super(properties.getAccess());
078                    this.properties= (UDFPropertiesImpl) properties;
079            }
080    
081            @Override
082            public long sizeOf() {
083                    return SizeOf.size(properties);
084            }
085        
086            
087            public UDF duplicate(ComponentImpl c) {
088                    UDFImpl udf = new UDFImpl(properties);
089                    udf.ownerComponent=c;
090                    udf.setAccess(getAccess());
091                    return udf;
092            }
093            
094            @Override
095            public UDF duplicate(boolean deepCopy) {
096                    return duplicate(ownerComponent);
097            }
098            
099            @Override
100            public UDF duplicate() {
101                    return duplicate(ownerComponent);
102            }
103    
104            @Override
105            public Object implementation(PageContext pageContext) throws Throwable {
106                    return ComponentUtil.getPage(pageContext, properties.pageSource).udfCall(pageContext,this,properties.index);
107            }
108    
109            private final Object castToAndClone(PageContext pc,FunctionArgument arg,Object value, int index) throws PageException {
110                    //if(value instanceof Array)print.out(count++);
111                    if(Decision.isCastableTo(arg.getType(),arg.getTypeAsString(),value)) 
112                            return arg.isPassByReference()?value:Duplicator.duplicate(value,false);
113                    throw new UDFCasterException(this,arg,value,index);
114                    //REALCAST return Caster.castTo(pc,arg.getType(),arg.getTypeAsString(),value);
115            }
116            private final Object castTo(FunctionArgument arg,Object value, int index) throws PageException {
117                    if(Decision.isCastableTo(arg.getType(),arg.getTypeAsString(),value)) return value;
118                    throw new UDFCasterException(this,arg,value,index);
119            }
120            
121            private void defineArguments(PageContext pc,FunctionArgument[] funcArgs, Object[] args,Argument newArgs) throws PageException {
122                    // define argument scope
123                    for(int i=0;i<funcArgs.length;i++) {
124                            // argument defined
125                            if(args.length>i) {
126                                    newArgs.setEL(funcArgs[i].getName(),castToAndClone(pc,funcArgs[i], args[i],i+1));
127                            }
128                            // argument not defined
129                            else {
130                                    Object d=getDefaultValue(pc,i,NullSupportHelper.NULL());
131                                    if(d==NullSupportHelper.NULL()) { 
132                                            if(funcArgs[i].isRequired()) {
133                                                    throw new ExpressionException("The parameter "+funcArgs[i].getName()+" to function "+getFunctionName()+" is required but was not passed in.");
134                                            }
135                                            if(!NullSupportHelper.full()) newArgs.setEL(funcArgs[i].getName(),Argument.NULL);
136                                    }
137                                    else {
138                                            newArgs.setEL(funcArgs[i].getName(),castTo(funcArgs[i],d,i+1));
139                                    }
140                            }
141                    }
142                    for(int i=funcArgs.length;i<args.length;i++) {
143                            newArgs.setEL(ArgumentIntKey.init(i+1),args[i]);
144                    }
145            }
146    
147            
148        private void defineArguments(PageContext pageContext, FunctionArgument[] funcArgs, Struct values, Argument newArgs) throws PageException {
149            // argumentCollection
150            argumentCollection(values,funcArgs);
151            //print.out(values.size());
152            Object value;
153            Collection.Key name;
154                    
155            for(int i=0;i<funcArgs.length;i++) {
156                            // argument defined
157                            name=funcArgs[i].getName();
158                            value=values.removeEL(name); 
159                            if(value!=null) {
160                                    newArgs.set(name,castToAndClone(pageContext,funcArgs[i], value,i+1));
161                                    continue;
162                            }
163                            value=values.removeEL(ArgumentIntKey.init(i+1)); 
164                            if(value!=null) {
165                                    newArgs.set(name,castToAndClone(pageContext,funcArgs[i], value,i+1));
166                                    continue;
167                            }
168                            
169                            
170                            // default argument or exception
171                            Object defaultValue=getDefaultValue(pageContext,i,NullSupportHelper.NULL());//funcArgs[i].getDefaultValue();
172                            if(defaultValue==NullSupportHelper.NULL()) { 
173                                    if(funcArgs[i].isRequired()) {
174                                            throw new ExpressionException("The parameter "+funcArgs[i].getName()+" to function "+getFunctionName()+" is required but was not passed in.");
175                                    }
176                                    newArgs.set(name,Argument.NULL);
177                            }
178                            else newArgs.set(name,castTo(funcArgs[i],defaultValue,i+1));    
179                    }
180                    
181                    
182                    Iterator<Entry<Key, Object>> it = values.entryIterator();
183            Entry<Key, Object> e;
184                    while(it.hasNext()) {
185                            e = it.next();
186                            newArgs.set(e.getKey(),e.getValue());
187                    }
188            }
189        
190    
191            public static void argumentCollection(Struct values) {
192                    argumentCollection(values,EMPTY);
193            }
194    
195            public static void argumentCollection(Struct values, FunctionArgument[] funcArgs) {
196                    Object value=values.removeEL(KeyConstants._argumentCollection);
197                    if(value !=null) {
198                            value=Caster.unwrap(value,value);
199                            
200                            if(value instanceof Argument) {
201                                    Argument argColl=(Argument) value;
202                                    Iterator<Key> it = argColl.keyIterator();
203                                    Key k;
204                                    int i=-1;
205                                while(it.hasNext()) {
206                                    i++;
207                                    k = it.next();
208                                    if(funcArgs.length>i && k instanceof ArgumentIntKey) {
209                                    if(!values.containsKey(funcArgs[i].getName()))
210                                            values.setEL(funcArgs[i].getName(),argColl.get(k,Argument.NULL));
211                                    else 
212                                            values.setEL(k,argColl.get(k,Argument.NULL));
213                                    }
214                            else if(!values.containsKey(k)){
215                                    values.setEL(k,argColl.get(k,Argument.NULL));
216                            }
217                        }
218                        }
219                            else if(value instanceof Collection) {
220                            Collection argColl=(Collection) value;
221                                //Collection.Key[] keys = argColl.keys();
222                                    Iterator<Key> it = argColl.keyIterator();
223                                    Key k;
224                                    while(it.hasNext()) {
225                                    k = it.next();
226                                    if(!values.containsKey(k)){
227                                    values.setEL(k,argColl.get(k,Argument.NULL));
228                            }
229                        }
230                        }
231                            else if(value instanceof Map) {
232                                    Map map=(Map) value;
233                                Iterator it = map.entrySet().iterator();
234                                Map.Entry entry;
235                                Key key;
236                                while(it.hasNext()) {
237                                    entry=(Entry) it.next();
238                                    key = toKey(entry.getKey());
239                                    if(!values.containsKey(key)){
240                                    values.setEL(key,entry.getValue());
241                            }
242                        }
243                        }
244                            else if(value instanceof java.util.List) {
245                                    java.util.List list=(java.util.List) value;
246                                Iterator it = list.iterator();
247                                Object v;
248                                int index=0;
249                                Key k;
250                                while(it.hasNext()) {
251                                    v= it.next();
252                                    k=ArgumentIntKey.init(++index);
253                                    if(!values.containsKey(k)){
254                                    values.setEL(k,v);
255                            }
256                        }
257                        }
258                        else {
259                            values.setEL(KeyConstants._argumentCollection,value);
260                        }
261                    } 
262            }
263            
264            public static Collection.Key toKey(Object obj) {
265                    if(obj==null) return null;
266                    if(obj instanceof Collection.Key) return (Collection.Key) obj;
267                    String str = Caster.toString(obj,null);
268                    if(str==null) return KeyImpl.init(obj.toString());
269                    return KeyImpl.init(str);
270            }
271    
272            @Override
273            public Object callWithNamedValues(PageContext pc, Struct values,boolean doIncludePath) throws PageException {
274            return this.properties.cachedWithin>0?
275                            _callCachedWithin(pc, null, values, doIncludePath):
276                            _call(pc, null, values, doIncludePath);
277        }
278    
279        @Override
280            public Object call(PageContext pc, Object[] args, boolean doIncludePath) throws PageException {
281            return  this.properties.cachedWithin>0?
282                            _callCachedWithin(pc, args,null, doIncludePath):
283                            _call(pc, args,null, doIncludePath);
284        }
285       // private static int count=0;
286        
287        
288    
289        private Object _callCachedWithin(PageContext pc, Object[] args, Struct values,boolean doIncludePath) throws PageException {
290            PageContextImpl pci=(PageContextImpl) pc;
291            String id = UDFUtil.callerHash(this,args,values);
292            
293                    Cache cache = Util.getDefault(pc,ConfigImpl.CACHE_DEFAULT_FUNCTION,DEFAULT_CACHE);      
294                    Object o =  cache.getValue(id,null);
295                    
296                    // get from cache
297                    if(o instanceof UDFCacheEntry ) {
298                            UDFCacheEntry entry = (UDFCacheEntry)o;
299                            //if(entry.creationdate+properties.cachedWithin>=System.currentTimeMillis()) {
300                                    try {
301                                            pc.write(entry.output);
302                                    } catch (IOException e) {
303                                            throw Caster.toPageException(e);
304                                    }
305                                    return entry.returnValue;
306                            //}
307                            
308                            //cache.remove(id);
309                    }
310            
311                    // execute the function
312                    BodyContent bc =  pci.pushBody();
313                
314                try {
315                    Object rtn = _call(pci, args, values, doIncludePath);
316                    String out = bc.getString();
317                    cache.put(id, new UDFCacheEntry(out, rtn),properties.cachedWithin,properties.cachedWithin);
318                    return rtn;
319                    }
320            finally {
321                    BodyContentUtil.flushAndPop(pc,bc);
322            }
323        }
324        
325        private Object _call(PageContext pc, Object[] args, Struct values,boolean doIncludePath) throws PageException {
326            
327            //print.out(count++);
328            PageContextImpl pci=(PageContextImpl) pc;
329            Argument newArgs= pci.getScopeFactory().getArgumentInstance();
330            newArgs.setFunctionArgumentNames(properties.argumentsSet);
331            LocalImpl newLocal=pci.getScopeFactory().getLocalInstance();
332            
333                    Undefined       undefined=pc.undefinedScope();
334                    Argument        oldArgs=pc.argumentsScope();
335            Local           oldLocal=pc.localScope();
336            
337                    pc.setFunctionScopes(newLocal,newArgs);
338                    
339                    int oldCheckArgs=undefined.setMode(properties.localMode==null?pc.getApplicationContext().getLocalMode():properties.localMode.intValue());
340                    PageSource psInc=null;
341                    try {
342                            PageSource ps = getPageSource();
343                            if(doIncludePath)psInc = ps;
344                            //if(!ps.getDisplayPath().endsWith("Dump.cfc"))print.e(getPageSource().getDisplayPath());
345                            if(doIncludePath && getOwnerComponent()!=null) {
346                                    //if(!ps.getDisplayPath().endsWith("Dump.cfc"))print.ds(ps.getDisplayPath());
347                                    psInc=ComponentUtil.getPageSource(getOwnerComponent());
348                                    if(psInc==pci.getCurrentTemplatePageSource()) {
349                                            psInc=null;
350                                    }
351                                    
352                            }
353                            pci.addPageSource(ps,psInc);
354                            pci.addUDF(this);
355                            
356    //////////////////////////////////////////
357                            BodyContent bc=null;
358                            Boolean wasSilent=null;
359                            boolean bufferOutput=getBufferOutput(pci);
360                            if(!getOutput()) {
361                                    if(bufferOutput) bc =  pci.pushBody();
362                                    else wasSilent=pc.setSilent()?Boolean.TRUE:Boolean.FALSE;
363                            }
364                            
365                        UDF parent=null;
366                        if(ownerComponent!=null) {
367                                parent=pci.getActiveUDF();
368                                pci.setActiveUDF(this);
369                        }
370                        Object returnValue = null;
371                        
372                        try {
373                            
374                            if(args!=null)  defineArguments(pc,getFunctionArguments(),args,newArgs);
375                                    else                    defineArguments(pc,getFunctionArguments(),values,newArgs);
376                            
377                                    returnValue=implementation(pci);
378                                    if(ownerComponent!=null)pci.setActiveUDF(parent);
379                            }
380                    catch(Throwable t) {
381                            if(ownerComponent!=null)pci.setActiveUDF(parent);
382                            if(!getOutput()) {
383                                    if(bufferOutput)BodyContentUtil.flushAndPop(pc,bc);
384                                    else if(!wasSilent)pc.unsetSilent();
385                            }
386                            //BodyContentUtil.flushAndPop(pc,bc);
387                            throw Caster.toPageException(t);
388                    }
389                    if(!getOutput()) {
390                            if(bufferOutput)BodyContentUtil.clearAndPop(pc,bc);
391                            else if(!wasSilent)pc.unsetSilent();
392                    }
393                    //BodyContentUtil.clearAndPop(pc,bc);
394                    
395                    
396                    
397                    
398                    if(properties.returnType==CFTypes.TYPE_ANY) return returnValue;
399                    else if(Decision.isCastableTo(properties.strReturnType,returnValue,false,false,-1)) return returnValue;
400                    else throw new UDFCasterException(this,properties.strReturnType,returnValue);
401                            //REALCAST return Caster.castTo(pageContext,returnType,returnValue,false);
402    //////////////////////////////////////////
403                            
404                    }
405                    finally {
406                            pc.removeLastPageSource(psInc!=null);
407                            pci.removeUDF();
408                pci.setFunctionScopes(oldLocal,oldArgs);
409                        undefined.setMode(oldCheckArgs);
410                pci.getScopeFactory().recycle(newArgs);
411                pci.getScopeFactory().recycle(newLocal);
412                    }
413            }
414    
415        @Override
416            public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
417                    return toDumpData(pageContext, maxlevel, dp,this,false);
418            }
419            public static DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp,UDF udf, boolean closure) {
420            
421                    if(!dp.getShowUDFs())
422                            return new SimpleDumpData(closure?"<Closure>":"<UDF>");
423                    
424                    // arguments
425                    FunctionArgument[] args = udf.getFunctionArguments();
426            
427            DumpTable atts = closure?new DumpTable("udf","#ff00ff","#ffccff","#000000"):new DumpTable("udf","#cc66ff","#ffccff","#000000");
428            
429                    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")}));
430                    for(int i=0;i<args.length;i++) {
431                            FunctionArgument arg=args[i];
432                            DumpData def;
433                            try {
434                                    Object oa=null;
435                    try {
436                        oa = UDFUtil.getDefaultValue(pageContext, (UDFPlus)udf, i, null);//udf.getDefaultValue(pageContext,i,null);
437                    } catch (PageException e1) {
438                    }
439                    if(oa==null)oa="null";
440                                    def=new SimpleDumpData(Caster.toString(oa));
441                            } catch (PageException e) {
442                                    def=new SimpleDumpData("");
443                            }
444                            atts.appendRow(new DumpRow(0,new DumpData[]{
445                                            new SimpleDumpData(arg.getDisplayName()),
446                                            new SimpleDumpData(arg.getName().getString()),
447                                            new SimpleDumpData(arg.isRequired()),
448                                            new SimpleDumpData(arg.getTypeAsString()),
449                                            def,
450                                            new SimpleDumpData(arg.getHint())}));
451                            //atts.setRow(0,arg.getHint());
452                            
453                    }
454                    
455                    DumpTable func = closure?new DumpTable("#ff00ff","#ffccff","#000000"):new DumpTable("#cc66ff","#ffccff","#000000");
456                    if(closure) func.setTitle("Closure");
457                    else {
458                            String f="Function ";
459                            try {
460                                    f=StringUtil.ucFirst(ComponentUtil.toStringAccess(udf.getAccess()).toLowerCase())+" "+f;
461                            } 
462                            catch (ExpressionException e) {}
463                            func.setTitle(f+udf.getFunctionName());
464                    }
465    
466                    if(udf instanceof UDFImpl)func.setComment("source:"+((UDFImpl)udf).getPageSource().getDisplayPath());
467    
468                    if(!StringUtil.isEmpty(udf.getDescription()))func.setComment(udf.getDescription());
469                    
470                    func.appendRow(1,new SimpleDumpData("arguments"),atts);
471                    func.appendRow(1,new SimpleDumpData("return type"),new SimpleDumpData(udf.getReturnTypeAsString()));
472                    
473                    boolean hasLabel=!StringUtil.isEmpty(udf.getDisplayName());//displayName!=null && !displayName.equals("");
474                    boolean hasHint=!StringUtil.isEmpty(udf.getHint());//hint!=null && !hint.equals("");
475                    
476                    if(hasLabel || hasHint) {
477                            DumpTable box = new DumpTable("#ffffff","#cccccc","#000000");
478                            box.setTitle(hasLabel?udf.getDisplayName():udf.getFunctionName());
479                            if(hasHint)box.appendRow(0,new SimpleDumpData(udf.getHint()));
480                            box.appendRow(0,func);
481                            return box;
482                    }
483                    return func;
484            }
485            
486            @Override
487            public String getDisplayName() {
488                    return properties.displayName;
489            }
490            
491            @Override
492            public String getHint() {
493                    return properties.hint;
494            }
495        
496            @Override
497            public PageSource getPageSource() {
498            return properties.pageSource;
499        }
500    
501            public Struct getMeta() {
502                    return properties.meta;
503            }
504            
505            @Override
506            public Struct getMetaData(PageContext pc) throws PageException {
507                    return ComponentUtil.getMetaData(pc, properties);
508                    //return getMetaData(pc, this);
509            }
510    
511            @Override
512            public Object getValue() {
513                    return this;
514            }
515    
516    
517            /**
518             * @param componentImpl the componentImpl to set
519             * @param injected 
520             */
521            public void setOwnerComponent(ComponentImpl component) {
522                    this.ownerComponent = component;
523            }
524            
525            @Override
526            public Component getOwnerComponent() {
527                    return ownerComponent;//+++
528            }
529            
530            @Override
531            public String toString() {
532                    StringBuffer sb=new StringBuffer(properties.functionName);
533                    sb.append("(");
534                    int optCount=0;
535                    for(int i=0;i<properties.arguments.length;i++) {
536                            if(i>0)sb.append(", ");
537                            if(!properties.arguments[i].isRequired()){
538                                    sb.append("[");
539                                    optCount++;
540                            }
541                            sb.append(properties.arguments[i].getTypeAsString());
542                            sb.append(" ");
543                            sb.append(properties.arguments[i].getName());
544                    }
545                    for(int i=0;i<optCount;i++){
546                            sb.append("]");
547                    }
548                    sb.append(")");
549                    return sb.toString();
550            }
551    
552            @Override
553            public Boolean getSecureJson() {
554                    return properties.secureJson;
555            }
556    
557            @Override
558            public Boolean getVerifyClient() {
559                    return properties.verifyClient;
560            }
561            
562            @Override
563            public Object clone() {
564                    return duplicate();
565            }
566    
567            @Override
568            public FunctionArgument[] getFunctionArguments() {
569            return properties.arguments;
570        }
571            
572            @Override
573            public Object getDefaultValue(PageContext pc,int index) throws PageException {
574            return UDFUtil.getDefaultValue(pc,properties.pageSource,properties.index,index,null);
575        }
576        
577        @Override
578            public Object getDefaultValue(PageContext pc,int index, Object defaultValue) throws PageException {
579            return UDFUtil.getDefaultValue(pc,properties.pageSource,properties.index,index,defaultValue);
580        }
581        // public abstract Object getDefaultValue(PageContext pc,int index) throws PageException;
582    
583        @Override
584            public String getFunctionName() {
585                    return properties.functionName;
586            }
587    
588            @Override
589            public boolean getOutput() {
590                    return properties.output;
591            }
592            
593            public Boolean getBufferOutput() {
594                    return properties.bufferOutput;
595            }
596            
597            private boolean getBufferOutput(PageContextImpl pc) {// FUTURE move to interface
598                    if(properties.bufferOutput!=null)
599                            return properties.bufferOutput.booleanValue();
600                    return ((ApplicationContextSupport)pc.getApplicationContext()).getBufferOutput();
601            }
602    
603            @Override
604            public int getReturnType() {
605                    return properties.returnType;
606            }
607            
608            @Override
609            public String getReturnTypeAsString() {
610                    return properties.strReturnType;
611            }
612            
613            @Override
614            public String getDescription() {
615                    return properties.description;
616            }
617            
618            @Override
619            public int getReturnFormat() {
620                    return properties.returnFormat;
621            }
622            
623            public final String getReturnFormatAsString() {
624                    return properties.strReturnFormat;
625            }
626            
627            
628            public static int toReturnFormat(String returnFormat) throws ExpressionException {
629                    if(StringUtil.isEmpty(returnFormat,true))
630                            return UDF.RETURN_FORMAT_WDDX;
631                            
632                            
633                    returnFormat=returnFormat.trim().toLowerCase();
634                    if("wddx".equals(returnFormat))                         return UDF.RETURN_FORMAT_WDDX;
635                    else if("json".equals(returnFormat))            return UDF.RETURN_FORMAT_JSON;
636                    else if("plain".equals(returnFormat))           return UDF.RETURN_FORMAT_PLAIN;
637                    else if("text".equals(returnFormat))            return UDF.RETURN_FORMAT_PLAIN;
638                    else if("serialize".equals(returnFormat))       return UDF.RETURN_FORMAT_SERIALIZE;
639                    else if("cfml".equals(returnFormat))    return UDF.RETURN_FORMAT_SERIALIZE;
640                    else if("cfm".equals(returnFormat))     return UDF.RETURN_FORMAT_SERIALIZE;
641                    else throw new ExpressionException("invalid returnFormat definition ["+returnFormat+"], valid values are [wddx,plain,json,cfml]");
642            }
643    
644            public static String toReturnFormat(int returnFormat) throws ExpressionException {
645                    if(RETURN_FORMAT_WDDX==returnFormat)            return "wddx";
646                    else if(RETURN_FORMAT_JSON==returnFormat)       return "json";
647                    else if(RETURN_FORMAT_PLAIN==returnFormat)      return "plain";
648                    else if(RETURN_FORMAT_SERIALIZE==returnFormat)  return "cfml";
649                    else throw new ExpressionException("invalid returnFormat definition, valid values are [wddx,plain,json,cfml]");
650            }
651            
652            public static String toReturnFormat(int returnFormat,String defaultValue) {
653                    if(RETURN_FORMAT_WDDX==returnFormat)            return "wddx";
654                    else if(RETURN_FORMAT_JSON==returnFormat)       return "json";
655                    else if(RETURN_FORMAT_PLAIN==returnFormat)      return "plain";
656                    else if(RETURN_FORMAT_SERIALIZE==returnFormat)  return "cfml";
657                    else return defaultValue;
658            }
659    
660            @Override
661            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
662                    // access
663                    setAccess(in.readInt());
664                    
665                    // properties
666                    properties=(UDFPropertiesImpl) in.readObject();
667            }
668    
669            @Override
670            public void writeExternal(ObjectOutput out) throws IOException {
671                    // access
672                    out.writeInt(getAccess());
673                    
674                    // properties
675                    out.writeObject(properties);
676            }
677            
678            @Override
679            public boolean equals(Object obj){
680                    if(!(obj instanceof UDF)) return false;
681                    return equals(this,(UDF)obj);
682            }
683            public static boolean equals(UDF left, UDF right){
684                    if(
685                            !left.getPageSource().equals(right.getPageSource())
686                            || !_eq(left.getFunctionName(),right.getFunctionName())
687                            || left.getAccess()!=right.getAccess()
688                            || !_eq(left.getFunctionName(),right.getFunctionName())
689                            || left.getOutput()!=right.getOutput()
690                            || left.getReturnFormat()!=right.getReturnFormat()
691                            || left.getReturnType()!=right.getReturnType()
692                            || !_eq(left.getReturnTypeAsString(),right.getReturnTypeAsString())
693                            || !_eq(left.getSecureJson(),right.getSecureJson())
694                            || !_eq(left.getVerifyClient(),right.getVerifyClient())
695                    ) return false;
696    
697                    // Arguments
698                    FunctionArgument[] largs = left.getFunctionArguments();
699                    FunctionArgument[] rargs = right.getFunctionArguments();
700                    if(largs.length!=rargs.length) return false;
701                    for(int i=0;i<largs.length;i++){
702                            if(!largs[i].equals(rargs[i]))return false;
703                    }
704                    
705                    
706                    
707                    
708                    return true;
709            }
710    
711            private static boolean _eq(Object left, Object right) {
712                    if(left==null) return right==null;
713                    return left.equals(right);
714            }
715            
716            public int getIndex(){
717                    return properties.index;
718            }
719            
720    }
721