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.op;
020
021import java.io.ByteArrayOutputStream;
022import java.io.InputStream;
023import java.net.URI;
024import java.sql.Blob;
025import java.sql.Clob;
026import java.text.DateFormat;
027import java.text.ParsePosition;
028import java.util.Calendar;
029import java.util.Date;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Locale;
033import java.util.Map;
034import java.util.TimeZone;
035import java.util.regex.Pattern;
036
037import lucee.commons.date.DateTimeUtil;
038import lucee.commons.date.JREDateTimeUtil;
039import lucee.commons.i18n.FormatUtil;
040import lucee.commons.lang.CFTypes;
041import lucee.commons.lang.StringUtil;
042import lucee.runtime.Component;
043import lucee.runtime.PageContext;
044import lucee.runtime.coder.Base64Util;
045import lucee.runtime.converter.WDDXConverter;
046import lucee.runtime.engine.ThreadLocalPageContext;
047import lucee.runtime.exp.ExpressionException;
048import lucee.runtime.exp.PageException;
049import lucee.runtime.ext.function.Function;
050import lucee.runtime.img.Image;
051import lucee.runtime.java.JavaObject;
052import lucee.runtime.net.mail.MailUtil;
053import lucee.runtime.net.rpc.AxisCaster;
054import lucee.runtime.net.rpc.Pojo;
055import lucee.runtime.op.date.DateCaster;
056import lucee.runtime.op.validators.ValidateCreditCard;
057import lucee.runtime.text.xml.XMLCaster;
058import lucee.runtime.text.xml.XMLUtil;
059import lucee.runtime.text.xml.struct.XMLStruct;
060import lucee.runtime.type.Array;
061import lucee.runtime.type.Closure;
062import lucee.runtime.type.Collection;
063import lucee.runtime.type.Collection.Key;
064import lucee.runtime.type.ObjectWrap;
065import lucee.runtime.type.Objects;
066import lucee.runtime.type.Query;
067import lucee.runtime.type.QueryColumn;
068import lucee.runtime.type.Struct;
069import lucee.runtime.type.UDF;
070import lucee.runtime.type.dt.DateTime;
071
072import org.w3c.dom.Document;
073import org.w3c.dom.Element;
074import org.w3c.dom.Node;
075import org.w3c.dom.NodeList;
076
077
078/**
079 * Object to test if a Object is a specific type
080 */
081public final class Decision {
082
083        private static final String STRING_DEFAULT_VALUE = "this is a unique string";
084
085        private static Pattern ssnPattern;
086        private static Pattern phonePattern;
087        private static Pattern zipPattern; 
088
089        /**
090         * tests if value is a simple value (Number,String,Boolean,Date,Printable)
091         * @param value value to test
092         * @return is value a simple value
093         */
094        public static boolean isSimpleValue(Object value){
095                return 
096                                (value instanceof Number) || 
097                                (value instanceof String) || 
098                                (value instanceof Boolean) || 
099                                (value instanceof Date) || 
100                                ((value instanceof Castable) && !(value instanceof Objects) && !(value instanceof Collection));
101        }
102        
103        /**
104         * tests if value is Numeric
105         * @param value value to test
106         * @return is value numeric
107         */
108        public static boolean isNumeric(Object value) {
109                if(value instanceof Number) return true;
110                else if(value instanceof String) {
111                        return isNumeric(value.toString());
112                }
113                
114                else return false;
115        }
116
117        public static boolean isCastableToNumeric(Object o) {
118                
119                if(isNumeric(o)) return true;
120                else if(isBoolean(o)) return true;
121                else if(isDateSimple(o,false)) return true;
122        else if(o == null) return true;
123        else if(o instanceof ObjectWrap) return isCastableToNumeric(((ObjectWrap)o).getEmbededObject("notanumber"));
124
125        else if(o instanceof Castable) {
126                return Decision.isValid(((Castable)o).castToDoubleValue(Double.NaN));
127                
128        }
129                return false;
130        }
131        
132        public static boolean isCastableToDate(Object o) {
133                if(isDateAdvanced(o, true)) return true;
134                else if(isBoolean(o)) return true;
135        
136        else if(o instanceof ObjectWrap) return isCastableToDate(((ObjectWrap)o).getEmbededObject("notadate"));
137
138        else if(o instanceof Castable) {
139                return ((Castable)o).castToDateTime(null)!=null;
140                
141        }
142                return false;
143        }
144
145        /**
146         * tests if value is Numeric
147         * @param value value to test
148         * @return is value numeric
149         */
150        public static boolean isNumeric(Object value, boolean alsoBooleans) {
151                if(alsoBooleans && isBoolean(value)) return true;
152                return isNumeric(value);
153        }
154        
155        /**
156         * tests if String value is Numeric
157         * @param str value to test
158         * @return is value numeric
159         */
160        public static boolean isNumeric(String str) {
161        if(str==null) return false; 
162        str=str.trim();
163        
164        int pos=0; 
165        int len=str.length(); 
166        if(len==0) return false; 
167        char curr=str.charAt(pos); 
168        
169        if(curr=='+' || curr=='-') { 
170                if(len==++pos) return false; 
171                curr=str.charAt(pos); 
172        }
173        
174        boolean hasDot=false; 
175        boolean hasExp=false; 
176        for(;pos<len;pos++) { 
177            curr=str.charAt(pos); 
178            if(curr<'0') {
179                if(curr=='.') { 
180                    if(pos+1>=len || hasDot) return false; 
181                    hasDot=true; 
182                } 
183                else return false;
184            }
185            else if(curr>'9') {
186                if(curr=='e' || curr=='E') { 
187                    if(pos+1>=len || hasExp) return false; 
188                    hasExp=true; 
189                    hasDot=true; 
190                }
191                else return false;
192            }
193        } 
194        if(hasExp){
195                try{
196                        Double.parseDouble(str);
197                        return true;
198                }
199                catch( NumberFormatException e){
200                        return false;
201                } 
202        }
203        return true; 
204    } 
205
206
207        public static boolean isInteger(Object value) {
208                return isInteger(value,false);          
209        }
210
211        public static boolean isInteger(Object value,boolean alsoBooleans) {
212                if(!alsoBooleans && value instanceof Boolean) return false;
213                double dbl = Caster.toDoubleValue(value,false,Double.NaN);
214                if(!Decision.isValid(dbl)) return false;
215                int i=(int)dbl;
216                return i==dbl;          
217        }
218
219         /** tests if String value is Hex Value
220         * @param str value to test
221         * @return is value numeric
222         */
223        public static boolean isHex(String str) { 
224      if(str==null || str.length()==0) return false; 
225      
226      for(int i=str.length()-1;i>=0;i--) {
227          char c=str.charAt(i);
228          if(!(c>='0' && c<='9')) {
229              c=Character.toLowerCase(c);
230              if(!(c=='a' || c=='b' || c=='c' || c=='d' || c=='e' || c=='f'))return false;
231          }
232      }
233      return true;
234        }
235
236        /** tests if String value is UUID Value
237         * @param obj value to test
238         * @return is value numeric
239         * @deprecated use instead <code>isUUId(Object obj)</code>
240         */
241        public static boolean isUUID(Object obj) { 
242                return isUUId(obj);
243        }
244        
245         /** tests if String value is UUID Value
246         * @param obj value to test
247         * @return is value numeric
248         */
249        public static boolean isUUId(Object obj) { 
250                String str=Caster.toString(obj,null);
251                if(str==null) return false;
252
253                if(str.length()==35) {      
254                return 
255                Decision.isHex(str.substring(0,8)) &&
256                str.charAt(8)=='-' &&
257                Decision.isHex(str.substring(9,13)) &&
258                str.charAt(13)=='-' &&
259                Decision.isHex(str.substring(14,18)) &&
260                str.charAt(18)=='-' &&
261                Decision.isHex(str.substring(19));
262            }
263                else if(str.length()==32)
264                        return Decision.isHex(str);
265                return false;
266        }
267        
268
269        /**
270         * @param obj
271         * @return
272         * @deprecated use instead <code>isGUId(Object)</code>
273         */
274        public static boolean isGUID(Object obj) { 
275                return isGUId(obj);
276        }
277
278        public static boolean isGUId(Object obj) { 
279                String str=Caster.toString(obj,null);
280                if(str==null) return false;
281
282        
283                // GUID
284            if(str.length()==36) {          
285                return 
286                Decision.isHex(str.substring(0,8)) &&
287                str.charAt(8)=='-' &&
288                Decision.isHex(str.substring(9,13)) &&
289                str.charAt(13)=='-' &&
290                Decision.isHex(str.substring(14,18)) &&
291                str.charAt(18)=='-' &&
292                Decision.isHex(str.substring(19,23)) &&
293                str.charAt(23)=='-' &&
294                Decision.isHex(str.substring(24));
295            }
296            return false;
297        }
298        
299
300        public static boolean isGUIdSimple(Object obj) { 
301                String str=Caster.toString(obj,null);
302                if(str==null) return false;
303
304        
305                // GUID
306            if(str.length()==36) {          
307                return 
308                str.charAt(8)=='-' &&
309                str.charAt(13)=='-' &&
310                str.charAt(18)=='-' &&
311                str.charAt(23)=='-';
312            }
313            return false;
314        }
315
316        /**
317         * tests if value is a Boolean (Numbers are not acctepeted)
318         * @param value value to test
319         * @return is value boolean
320         */
321        public static boolean isBoolean(Object value) {
322        if(value instanceof Boolean) return true;
323        else if(value instanceof String) {
324                        return isBoolean(value.toString());
325                }
326        else if(value instanceof ObjectWrap) return isBoolean(((ObjectWrap)value).getEmbededObject(null));
327                else return false;
328        }
329
330        public static boolean isCastableToBoolean(Object value) {
331                if(value instanceof Boolean) return true;
332                if(value instanceof Number) return true;
333        else if(value instanceof String) {
334                String str = (String)value;
335                        return isBoolean(str) || isNumeric(str);
336                }
337        else if(value instanceof Castable) {
338            return ((Castable)value).castToBoolean(null)!=null;
339            
340        }
341        else if(value instanceof ObjectWrap) return isCastableToBoolean(((ObjectWrap)value).getEmbededObject(null));
342                else return false;
343        }
344        
345        public static boolean isBoolean(Object value, boolean alsoNumbers) {
346                if(isBoolean(value)) return true;
347        else if(alsoNumbers) return isNumeric(value);
348        else return false;
349        }
350
351    /**
352     * tests if value is a Boolean
353     * @param str value to test
354     * @return is value boolean
355     */
356    public static boolean isBoolean(String str) {
357        //str=str.trim();
358        if(str.length()<2) return false;
359        
360        switch(str.charAt(0)) {
361            case 't':
362            case 'T': return str.equalsIgnoreCase("true");
363            case 'f':
364            case 'F': return str.equalsIgnoreCase("false");
365            case 'y':
366            case 'Y': return str.equalsIgnoreCase("yes");
367            case 'n':
368            case 'N': return str.equalsIgnoreCase("no");
369        }
370        return false;
371    }   
372
373        /**
374         * tests if value is DateTime Object
375         * @param value value to test
376         * @param alsoNumbers interpret also a number as date
377         * @return is value a DateTime Object
378         */
379    public static boolean isDate(Object value,boolean alsoNumbers) {
380        return isDateSimple(value, alsoNumbers);
381    }
382
383        public static boolean isDateSimple(Object value,boolean alsoNumbers) {
384                return isDateSimple(value, alsoNumbers, false);
385        }
386        public static boolean isDateSimple(Object value,boolean alsoNumbers, boolean alsoMonthString) {
387                
388        //return DateCaster.toDateEL(value)!=null;
389                if(value instanceof DateTime)           return true;
390                else if(value instanceof Date)          return true;
391                // wrong timezone but this isent importend because date will not be importend
392                else if(value instanceof String)        return DateCaster.toDateSimple(value.toString(),alsoNumbers?DateCaster.CONVERTING_TYPE_OFFSET:DateCaster.CONVERTING_TYPE_NONE,alsoMonthString,TimeZone.getDefault(),null)!=null;
393                else if(value instanceof ObjectWrap) {
394                return isDateSimple(((ObjectWrap)value).getEmbededObject(null),alsoNumbers);
395        }
396        else if(value instanceof Castable)      {
397                    return ((Castable)value).castToDateTime(null)!=null;
398            
399                }
400                else if(alsoNumbers && value instanceof Number) return true;
401                else if(value instanceof Calendar) return true;
402                return false;
403        }
404        
405        public static boolean isDateAdvanced(Object value,boolean alsoNumbers) {
406            //return DateCaster.toDateEL(value)!=null;
407                if(value instanceof DateTime)           return true;
408                else if(value instanceof Date)          return true;
409                // wrong timezone but this isent importend because date will not be importend
410                else if(value instanceof String)        return DateCaster.toDateAdvanced(value.toString(),alsoNumbers?DateCaster.CONVERTING_TYPE_OFFSET:DateCaster.CONVERTING_TYPE_NONE,TimeZone.getDefault(),null)!=null;
411                else if(value instanceof Castable)      {
412                    return ((Castable)value).castToDateTime(null)!=null;
413             
414                }
415                else if(alsoNumbers && value instanceof Number) return true;
416                else if(value instanceof ObjectWrap) {
417                return isDateAdvanced(((ObjectWrap)value).getEmbededObject(null),alsoNumbers);
418        }
419        else if(value instanceof Calendar) return true;
420                return false;
421        }
422        
423        private static char[] DATE_DEL=new char[]{'.','/','-'};
424        
425        public static boolean isUSDate(Object value) {
426                String str = Caster.toString(value,"");
427                return isUSorEuroDateEuro(str,false);
428        }
429        
430        public static boolean isUSDate(String str) {
431                return isUSorEuroDateEuro(str,false);
432        }
433        
434        public static boolean isEuroDate(Object value) {
435                String str = Caster.toString(value,"");
436                return isUSorEuroDateEuro(str,true);
437        }
438        
439        public static boolean isEuroDate(String str) {
440                return isUSorEuroDateEuro(str,true);
441        }
442        
443        private static boolean isUSorEuroDateEuro(String str, boolean isEuro) {
444                if(StringUtil.isEmpty(str)) return false;
445                
446                for(int i=0;i<DATE_DEL.length;i++) {
447                        Array arr = lucee.runtime.type.util.ListUtil.listToArrayRemoveEmpty(str,DATE_DEL[i]);
448                        if(arr.size()!=3) continue;
449
450                        int month=Caster.toIntValue(    arr.get(isEuro?2:1,Constants.INTEGER_0),Integer.MIN_VALUE);
451                        int day=Caster.toIntValue(              arr.get(isEuro?1:2,Constants.INTEGER_0),Integer.MIN_VALUE);
452                        int year=Caster.toIntValue(             arr.get(3,Constants.INTEGER_0),Integer.MIN_VALUE);
453
454                        
455                        if(month==Integer.MIN_VALUE) continue;
456                        if(month>12) continue;
457                        if(day==Integer.MIN_VALUE) continue;
458                        if(day>31) continue;
459                        if(year==Integer.MIN_VALUE) continue;
460                        if(DateTimeUtil.getInstance().toTime(null,year, month, day, 0, 0, 0,0, Long.MIN_VALUE)==Long.MIN_VALUE) continue;
461                        return true;
462                }
463                return false;
464        }
465        
466        public static boolean isCastableToStruct(Object o) {
467                if(isStruct(o)) return true;
468                if(o == null) return false;
469                else if(o instanceof ObjectWrap) {
470                if(o instanceof JavaObject ) return true;
471            return isCastableToStruct(((ObjectWrap)o).getEmbededObject(null));
472        }
473                if(Decision.isSimpleValue(o)){
474                        return false;
475                }
476                //if(isArray(o) || isQuery(o)) return false;
477        return false;
478        }
479
480        /**
481         * tests if object is a struct 
482         * @param o
483         * @return is struct or not
484         */
485        public static boolean isStruct(Object o) {
486                if(o instanceof Struct) return true;
487        else if(o instanceof Map)return true;
488        else if(o instanceof Node)return true;
489                return false;
490        }
491
492        
493        /**
494         * can this type be casted to a array
495         * @param o
496         * @return
497         * @throws PageException
498         */
499        public static boolean isCastableToArray(Object o) {
500        if(isArray(o)) return true;
501        //else if(o instanceof XMLStruct) return true;
502        else if(o instanceof Struct) {
503            Struct sct=(Struct) o;
504            Iterator<Key> it = sct.keyIterator();
505
506            while (it.hasNext()) {
507                if (!isInteger( it.next(), false ))
508                        return false;
509            }
510            return true;
511        }
512        return false;
513    }
514        
515        /**
516         * tests if object is a array 
517         * @param o
518         * @return is array or not
519         */
520        public static boolean isArray(Object o) {
521                if(o instanceof Array)                          return true;
522                if(o instanceof List)                           return true;
523                if(isNativeArray(o))                            return true;
524                if(o instanceof ObjectWrap) {
525            return isArray(((ObjectWrap)o).getEmbededObject(null));
526        }
527        return false;
528        }
529
530        /**
531         * tests if object is a native java array 
532         * @param o
533         * @return is a native (java) array
534         */
535        public static boolean isNativeArray(Object o) {
536                //return o.getClass().isArray();
537                if(o instanceof Object[])                       return true;
538                else if(o instanceof boolean[])         return true;
539                else if(o instanceof byte[])            return true;
540                else if(o instanceof char[])            return true;
541                else if(o instanceof short[])           return true;
542                else if(o instanceof int[])                     return true;
543                else if(o instanceof long[])            return true;
544                else if(o instanceof float[])           return true;
545                else if(o instanceof double[])          return true;
546                return false;
547        }
548
549        /**
550         * tests if object is catable to a binary  
551         * @param object
552         * @return boolean
553         */
554        public static boolean isCastableToBinary(Object object,boolean checkBase64String) {
555                if(isBinary(object))return true;
556                if(object instanceof InputStream) return true;
557                if(object instanceof ByteArrayOutputStream) return true;
558                if(object instanceof Blob) return true;
559        
560                // Base64 String
561                if(!checkBase64String) return false;
562                String str = Caster.toString(object,null);
563        if(str==null) return false;
564        return Base64Util.isBase64(str);
565                
566        }
567
568        /**
569         * tests if object is a binary  
570         * @param object
571         * @return boolean
572         */
573        public static boolean isBinary(Object object) {
574                if(object instanceof byte[]) return true;
575                if(object instanceof ObjectWrap) return isBinary(((ObjectWrap)object).getEmbededObject(""));
576                return false;
577        }
578
579        /**
580         * tests if object is a Component  
581         * @param object
582         * @return boolean
583         */
584        public static boolean isComponent(Object object) {
585                return object instanceof Component;
586        }
587
588        /**
589         * tests if object is a Query  
590         * @param object
591         * @return boolean
592         */
593        public static boolean isQuery(Object object) {
594                if(object instanceof Query)return true;
595                else if(object instanceof ObjectWrap) {
596            return isQuery(((ObjectWrap)object).getEmbededObject(null));
597        }
598        return false;
599        }
600        public static boolean isQueryColumn(Object object) {
601                if(object instanceof QueryColumn)return true;
602                else if(object instanceof ObjectWrap) {
603            return isQueryColumn(((ObjectWrap)object).getEmbededObject(null));
604        }
605        return false;
606        }
607
608        /**
609         * tests if object is a binary  
610         * @param object
611         * @return boolean
612         */
613        public static boolean isUserDefinedFunction(Object object) {
614                return object instanceof UDF;
615        }
616        
617        /**
618         * tests if year is a leap year 
619         * @param year year to check
620         * @return boolean
621         */
622        public static final boolean isLeapYear(int year) {
623                return DateTimeUtil.getInstance().isLeapYear(year);
624                //return new GregorianCalendar().isLeapYear(year);
625    }
626        
627        /**
628         * tests if object is a WDDX Object 
629         * @param o Object to check
630         * @return boolean
631         */
632        public static boolean isWddx(Object o) {
633                if(!(o instanceof String)) return false;
634                String str=o.toString();
635                if(!(str.indexOf("wddxPacket")>0)) return false;
636                
637                // wrong timezone but this isent importend because date will not be used
638                WDDXConverter converter =new WDDXConverter(TimeZone.getDefault(),false,true);
639                try {
640                        converter.deserialize(Caster.toString(o),true);
641                } 
642                catch (Exception e) {
643                        return false;
644                }
645                return true;
646        }
647        
648        /**
649         * tests if object is a XML Object 
650         * @param o Object to check
651         * @return boolean
652         */
653        public static boolean isXML(Object o) {
654                if(o instanceof Node || o instanceof NodeList) return true;
655                if(o instanceof ObjectWrap) {
656            return isXML(((ObjectWrap)o).getEmbededObject(null));
657        }
658        try {
659            XMLCaster.toXMLStruct(XMLUtil.parse(XMLUtil.toInputSource(null, o),null,false),false);
660            return true;
661        }
662        catch(Exception outer) {
663            return false;
664        }
665                
666        }
667        
668        public static boolean isVoid(Object o) {
669                if(o==null)return true;
670        else if(o instanceof String)    return o.toString().length()==0;
671        else if(o instanceof Number)    return ((Number)o).intValue()==0;
672        else if(o instanceof Boolean)   return ((Boolean)o).booleanValue()==false ;
673        else if(o instanceof ObjectWrap)return isVoid(((ObjectWrap)o).getEmbededObject(("isnotnull")));
674                return false;
675        }
676        
677        
678        /**
679         * tests if object is a XML Element Object 
680         * @param o Object to check
681         * @return boolean
682         */
683        public static boolean isXMLElement(Object o) {
684                return o instanceof Element;
685        }
686        
687        /**
688         * tests if object is a XML Document Object 
689         * @param o Object to check
690         * @return boolean
691         */
692        public static boolean isXMLDocument(Object o) {
693                return o instanceof Document;
694        }
695        
696        /**
697         * tests if object is a XML Root Element Object 
698         * @param o Object to check
699         * @return boolean
700         */
701        public static boolean isXMLRootElement(Object o) {
702                if(o instanceof Node) {
703                        Node n=(Node)o;
704                        if(n instanceof XMLStruct)n=((XMLStruct)n).toNode();
705                        return n.getOwnerDocument()!=null && n.getOwnerDocument().getDocumentElement()==n;
706                }
707                return false;
708        }
709
710        /**
711         * @param obj
712         * @return returns if string represent a variable name
713         */
714        public static boolean isVariableName(Object obj) {
715                if(obj instanceof String) return isVariableName((String)obj);
716                return false;
717        }
718
719        public static boolean isFunction(Object obj) {
720                if(obj instanceof UDF)return true;
721                else if(obj instanceof ObjectWrap) {
722            return isFunction(((ObjectWrap)obj).getEmbededObject(null));
723        }
724        return false;
725        }
726
727        public static boolean isClosure(Object obj) {
728                if(obj instanceof Closure)return true;
729                else if(obj instanceof ObjectWrap) {
730            return isClosure(((ObjectWrap)obj).getEmbededObject(null));
731        }
732        return false;
733        }
734
735        /**
736         * @param string
737         * @return returns if string represent a variable name
738         */
739        public static boolean isVariableName(String string) {
740                if(string.length()==0)return false;
741                int len=string.length();
742                int pos=0;
743                while(pos<len) {
744                    char first=string.charAt(pos);
745                    if(!((first>='a' && first<='z')||(first>='A' && first<='Z')||(first=='_')))
746                                return false;
747                    pos++;
748                    for(;pos<len;pos++) {
749                                char c=string.charAt(pos);
750                                if(!((c>='a' && c<='z')||(c>='A' && c<='Z')||(c>='0' && c<='9')||(c=='_')))
751                                        break;
752                        }
753                    if(pos==len) return true;
754                    if(string.charAt(pos)=='.')pos++;
755                }
756                return false;
757        }
758        
759        /**
760         * @param string
761         * @return returns if string represent a variable name
762         */
763        public static boolean isSimpleVariableName(String string) {
764                if(string.length()==0)return false;
765                
766                char first=string.charAt(0);
767                if(!((first>='a' && first<='z')||(first>='A' && first<='Z')||(first=='_')))
768                        return false;
769                for(int i=string.length()-1;i>0;i--) {
770                        char c=string.charAt(i);
771                        if(!((c>='a' && c<='z')||(c>='A' && c<='Z')||(c>='0' && c<='9')||(c=='_')))
772                                return false;
773                }       
774                return true;
775        }
776        
777        /**
778         * @param key
779         * @return returns if string represent a variable name
780         */
781        public static boolean isSimpleVariableName(Collection.Key key) {
782                String strKey = key.getLowerString();
783                if(strKey.length()==0)return false;
784                
785                char first=strKey.charAt(0);
786                if(!((first>='a' && first<='z')||(first=='_')))
787                        return false;
788                for(int i=strKey.length()-1;i>0;i--) {
789                        char c=strKey.charAt(i);
790                        if(!((c>='a' && c<='z')||(c>='0' && c<='9')||(c=='_')))
791                                return false;
792                }       
793                return true;
794        }
795
796        /**
797         * returns if object is a CFML object
798         * @param o Object to check
799         * @return is or not
800         */
801        public static boolean isObject(Object o) {
802                return isComponent(o)
803                
804                        || (!isArray(o)
805                        && !isQuery(o)
806                        && !isSimpleValue(o)
807                        && !isStruct(o)
808                        && !isUserDefinedFunction(o)
809                        && !isXML(o));
810        }
811
812    /**
813     * @param obj
814     * @return return if a String is "Empty", that means NULL or String with length 0 (whitespaces will not counted) 
815     */
816    public static boolean isEmpty(Object obj) {
817        if(obj instanceof String)return StringUtil.isEmpty((String)obj);
818        return obj==null;
819    }
820
821
822    /**
823     * @deprecated use instead <code>StringUtil.isEmpty(String)</code>
824     * @param str
825     * @return return if a String is "Empty", that means NULL or String with length 0 (whitespaces will not counted) 
826     */
827    public static boolean isEmpty(String str) {
828        return StringUtil.isEmpty(str);
829    }
830    
831    /**
832     * @deprecated use instead <code>StringUtil.isEmpty(String)</code>
833     * @param str
834     * @param trim 
835     * @return return if a String is "Empty", that means NULL or String with length 0 (whitespaces will not counted) 
836     */
837    public static boolean isEmpty(String str, boolean trim) {
838        return StringUtil.isEmpty(str,trim);
839    }
840    
841
842        /**
843         * returns if a value is a credit card
844         * @param value
845         * @return is credit card
846         */
847        public static boolean isCreditCard(Object value) {
848                return ValidateCreditCard.isValid(Caster.toString(value,"0"));
849        }
850        
851
852        /**
853         * returns if given object is a email
854         * @param value
855         * @return
856         */
857        public static boolean isEmail(Object value) {
858
859        return MailUtil.isValidEmail(value);
860        }       
861        
862        
863        
864        /**
865         * returns if given object is a social security number (usa)
866         * @param value
867         * @return
868         */
869        public static boolean isSSN(Object value) {
870                String str = Caster.toString(value,null);
871                if(str==null)return false;
872                
873                if(ssnPattern==null)
874                        ssnPattern=Pattern.compile("^[0-9]{3}[-|]{1}[0-9]{2}[-|]{1}[0-9]{4}$");
875                
876                return ssnPattern.matcher(str.trim()).matches();
877                
878        }
879        
880        /**
881         * returns if given object is a phone
882         * @param value
883         * @return
884         */
885        public static boolean isPhone(Object value) {
886                String str = Caster.toString(value,null);
887                if(str==null)return false;
888                
889                if(phonePattern==null)
890                        phonePattern=Pattern.compile("^(\\+?1?[ \\-\\.]?([\\(]?([1-9][0-9]{2})[\\)]?))?[ ,\\-,\\.]?([^0-1]){1}([0-9]){2}[ ,\\-,\\.]?([0-9]){4}(( )((x){0,1}([0-9]){1,5}){0,1})?$");
891                return phonePattern.matcher(str.trim()).matches();
892                
893        }       
894
895        /**
896         * returns true if the given object is a valid URL
897         * @param value
898         * @return
899         */
900    public static boolean isURL( Object value ) {
901
902        String str = Caster.toString( value, null );
903
904        if ( str == null )                      return false;
905        if ( str.indexOf( ':' ) == -1 )         return false;
906
907        str = str.toLowerCase().trim();
908
909        if ( !str.startsWith( "http://" )
910          && !str.startsWith( "https://" )
911          && !str.startsWith( "file://" )
912          && !str.startsWith( "ftp://" )
913          && !str.startsWith( "mailto:" )
914          && !str.startsWith( "news:" )
915          && !str.startsWith( "urn:" ) )        return false;
916
917        try {
918
919            URI uri = new URI( str );
920            String proto = uri.getScheme();
921
922            if ( proto == null )                return false;
923
924            if ( proto.equals( "http" ) || proto.equals( "https" ) || proto.equals( "file" ) || proto.equals( "ftp" ) ) {
925
926                if ( uri.getHost() == null )    return false;
927
928                String path = uri.getPath();
929                if ( path != null ) {
930
931                    int len = path.length();
932                    for ( int i=0; i<len; i++ ) {
933
934                        if ( "?<>:*|\"".indexOf( path.charAt( i ) ) > -1 )
935                            return false;
936                    }
937                }
938            }
939
940            return true;
941        }
942        catch ( Exception ex ) {
943
944            return false;
945        }
946    }
947        
948        /**
949         * returns if given object is a zip code
950         * @param value
951         * @return
952         */
953        public static boolean isZipCode(Object value) {
954                String str = Caster.toString(value,null);
955                if(str==null)return false;
956                
957                if(zipPattern==null)
958                        zipPattern=Pattern.compile("([0-9]{5,5})|([0-9]{5,5}[- ]{1}[0-9]{4,4})");
959                return zipPattern.matcher(str.trim()).matches();
960        }
961
962        public static boolean isString(Object o) {
963                if(o instanceof String)                 return true;
964        else if(o instanceof Boolean)   return true;
965        else if(o instanceof Number)    return true;
966        else if(o instanceof Date)              return true;
967        else if(o instanceof Castable) {
968            return ((Castable)o).castToString(STRING_DEFAULT_VALUE)!=STRING_DEFAULT_VALUE;
969            
970        }
971        else if(o instanceof Clob)              return true;
972        else if(o instanceof Node)              return true;
973        else if(o instanceof Map || o instanceof List || o instanceof Function) return false;
974        else if(o == null) return true;
975        else if(o instanceof ObjectWrap) return isString(((ObjectWrap)o).getEmbededObject(""));
976                return true;
977        }
978        public static boolean isCastableToString(Object o) {
979                return isString(o);
980        }
981        
982    
983    public static boolean isValid(String type, Object value) throws ExpressionException {
984        type=StringUtil.toLowerCase(type.trim());
985        char first = type.charAt(0);
986        switch(first) {
987        case 'a':
988                if("any".equals(type))                  return true;//isSimpleValue(value);
989                if("array".equals(type))                return isArray(value);
990        break;
991        case 'b':
992                if("binary".equals(type))               return isBinary(value);
993                if("boolean".equals(type))              return isBoolean(value,true);
994        break;
995        case 'c':
996                if("creditcard".equals(type))   return isCreditCard(value);
997                if("component".equals(type))    return isComponent(value);
998                if("cfc".equals(type))                  return isComponent(value);
999        break;
1000        case 'd':
1001                if("date".equals(type))                 return isDateAdvanced(value,true);  // ist zwar nicht logisch aber ident. zu Neo
1002                if("datetime".equals(type))                     return isDateAdvanced(value,true);  // ist zwar nicht logisch aber ident. zu Neo
1003                if("double".equals(type))               return isCastableToNumeric(value);
1004        break;
1005        case 'e':
1006                if("eurodate".equals(type))             return isEuroDate(value); 
1007                if("email".equals(type))                return isEmail(value);
1008        break;
1009        case 'f':
1010                if("float".equals(type))                return isNumeric(value,true);
1011                if("function".equals(type))             return isFunction(value);
1012        break;
1013        case 'g':
1014                if("guid".equals(type))                 return isGUId(value);
1015        break;
1016        case 'i':
1017                if("integer".equals(type))              return isInteger(value,false);
1018                if("image".equals(type))                return Image.isImage(value);
1019        break;
1020        case 'n':
1021                if("numeric".equals(type))              return isCastableToNumeric(value);
1022                if("number".equals(type))               return isCastableToNumeric(value);
1023                if("node".equals(type))                 return isXML(value); 
1024        break;
1025        case 'p':
1026                if("phone".equals(type))                return isPhone(value);
1027        break;
1028        case 'q':
1029                if("query".equals(type))                return isQuery(value);
1030        break;
1031        case 's':
1032                if("simple".equals(type))               return isSimpleValue(value);    
1033                if("struct".equals(type))               return isStruct(value);
1034                if("ssn".equals(type))                  return isSSN(value);
1035                if("social_security_number".equals(type))return isSSN(value);
1036                if("string".equals(type))               return isString(value);
1037        break;
1038        case 't':
1039                if("telephone".equals(type))    return isPhone(value);
1040                if("time".equals(type))                 return isDateAdvanced(value,false);
1041        break;
1042        case 'u':
1043                if("usdate".equals(type))               return isUSDate(value); 
1044                if("uuid".equals(type))                 return isUUId(value);
1045                if("url".equals(type))                  return isURL(value);
1046        break;
1047        case 'v':
1048                if("variablename".equals(type)) return isVariableName(Caster.toString(value,""));
1049        break;
1050        case 'x':
1051                if("xml".equals(type))  return isXML(value); // DIFF 23
1052        break;
1053        case 'z':
1054                if("zip".equals(type))                  return isZipCode(value);
1055                if("zipcode".equals(type))              return isZipCode(value);
1056        break;
1057        }
1058        throw new ExpressionException("invalid type ["+type+"], valid types are [any,array,binary,boolean,component,creditcard,date,time,email,eurodate,float,numeric,guid,integer,query,simple,ssn,string,struct,telephone,URL,UUID,USdate,variableName,zipcode]");
1059                
1060    }
1061    
1062    /**
1063     * checks if a value is castable to a certain type
1064     * @param type any,array,boolean,binary, ...
1065     * @param o value to check
1066     * @param alsoPattern also check patterns like creditcards,email,phone ...
1067     * @param maxlength only used for email,url, string, ignored otherwise
1068     * @return
1069     */
1070    public static boolean isCastableTo(String type, Object o, boolean alsoAlias, boolean alsoPattern, int maxlength) {
1071        
1072        type=StringUtil.toLowerCase(type).trim();
1073        if(type.length()>2) {
1074            char first=type.charAt(0);
1075            switch(first) {
1076                case 'a':
1077                    if(type.equals("any")) {
1078                        return true;
1079                    }
1080                    else if(type.equals("array")) {
1081                        return isCastableToArray(o);
1082                    }
1083                    break;
1084                case 'b':
1085                    if(type.equals("boolean") || (alsoAlias && type.equals("bool"))) {
1086                        return isCastableToBoolean(o);
1087                    }
1088                    else if(type.equals("binary")) {
1089                        return isCastableToBinary(o,true);
1090                    }
1091                    else if(alsoAlias && type.equals("bigint")) {
1092                        return isCastableToNumeric(o);
1093                    }
1094                    else if(type.equals("base64")) {
1095                        return Caster.toBase64(o,null,null)!=null;
1096                    }
1097                    break;
1098                case 'c':
1099                        if(alsoPattern && type.equals("creditcard")) {
1100                        return Caster.toCreditCard(o,null)!=null;
1101                    }
1102                        if(alsoPattern && type.equals("char")) {
1103                                if(maxlength>-1) {
1104                                String str = Caster.toString(o,null);
1105                                if(str==null) return false;
1106                                return str.length()<=maxlength;
1107                        }
1108                        return isCastableToString(o);
1109                    }
1110                    break;
1111                case 'd':
1112                        if(type.equals("date")) {
1113                        return isDateAdvanced(o, true);
1114                    }
1115                    else if(type.equals("datetime")) {
1116                        return isDateAdvanced(o, true);
1117                    }
1118                    else if(alsoAlias && type.equals("double")) {
1119                        return isCastableToNumeric(o);
1120                    }
1121                    else if(alsoAlias && type.equals("decimal")) {
1122                        return Caster.toDecimal(o,null)!=null;
1123                    }
1124                    break;
1125                case 'e':
1126                    if(alsoAlias && type.equals("eurodate")) {
1127                        return isDateAdvanced(o, true);
1128                    }
1129                    else if(alsoPattern && type.equals("email")) {
1130                        if(maxlength>-1) {
1131                                String str = Caster.toEmail(o,null);
1132                                if(str==null) return false;
1133                                return str.length()<=maxlength;
1134                        }
1135                        return Caster.toEmail(o,null)!= null;
1136                    }
1137                    break;
1138                case 'f':
1139                    if(alsoAlias && type.equals("float")) {
1140                        return isCastableToNumeric(o);
1141                    }
1142                    if(type.equals("function")) {
1143                        return isFunction(o);
1144                    }
1145                    break;
1146                case 'g':
1147                    if(type.equals("guid")) {
1148                        return isGUId(o);
1149                    }
1150                    break;
1151                case 'i':
1152                    if(alsoAlias && (type.equals("integer") || type.equals("int"))) {
1153                        return isCastableToNumeric(o);
1154                    }
1155                    break;
1156                case 'l':
1157                    if(alsoAlias && type.equals("long")) {
1158                        return isCastableToNumeric(o);
1159                    }
1160                    break;
1161                case 'n':
1162                    if(type.equals("numeric")) {
1163                        return isCastableToNumeric(o);
1164                    }
1165                    else if(type.equals("number")) {
1166                        return isCastableToNumeric(o);
1167                    }
1168                    
1169                    if(alsoAlias) {
1170                                    if(type.equals("node")) return isXML(o);
1171                                    else if(type.equals("nvarchar") || type.equals("nchar")) {
1172                                        if(maxlength>-1) {
1173                                        String str = Caster.toString(o,null);
1174                                        if(str==null) return false;
1175                                        return str.length()<=maxlength;
1176                                }
1177                                return isCastableToString(o);
1178                                    }
1179                            }
1180                    
1181                    break;
1182                case 'o':
1183                    if(type.equals("object")) {
1184                        return true;
1185                    }
1186                    else if(alsoAlias && type.equals("other")) {
1187                        return true;
1188                    }
1189                    break;
1190                case 'p':
1191                    if(alsoPattern && type.equals("phone")) {
1192                        return Caster.toPhone(o,null)!=null;
1193                    }
1194                    break;
1195                case 'q':
1196                    if(type.equals("query")) {
1197                        return isQuery(o);
1198                    }
1199                    
1200                            if(type.equals("querycolumn")) return isQueryColumn(o);
1201                            
1202                    break;
1203                case 's':
1204                    if(type.equals("string")) {
1205                        if(maxlength>-1) {
1206                                String str = Caster.toString(o,null);
1207                                if(str==null) return false;
1208                                return str.length()<=maxlength;
1209                        }
1210                        return isCastableToString(o);
1211                    }
1212                    else if(type.equals("struct")) {
1213                        return isCastableToStruct(o);
1214                    }
1215                    else if(alsoAlias && type.equals("short")) {
1216                        return isCastableToNumeric(o);
1217                    }
1218                    else if(alsoPattern && (type.equals("ssn") ||type.equals("social_security_number"))) {
1219                        return Caster.toSSN(o,null)!=null;
1220                    }
1221                    break;
1222                case 't':
1223                    if(type.equals("timespan")) {
1224                        return Caster.toTimespan(o,null)!=null;
1225                    }
1226                    if(type.equals("time")) {
1227                        return isDateAdvanced(o, true);
1228                    }
1229                    if(alsoPattern && type.equals("telephone")) {
1230                        return Caster.toPhone(o,null)!=null;
1231                    }
1232                    
1233
1234                            if(alsoAlias && type.equals("timestamp")) return isDateAdvanced(o, true);
1235                            if(alsoAlias && type.equals("text")) {
1236                                if(maxlength>-1) {
1237                                String str = Caster.toString(o,null);
1238                                if(str==null) return false;
1239                                return str.length()<=maxlength;
1240                        }
1241                        return isCastableToString(o);
1242                            }
1243                            
1244                case 'u':
1245                    if(type.equals("uuid")) {
1246                        return isUUId(o);
1247                    }
1248                    if(alsoAlias && type.equals("usdate")) {
1249                        return isDateAdvanced(o, true);
1250                    }
1251                    if(alsoPattern && type.equals("url")) {
1252                        if(maxlength>-1) {
1253                                String str = Caster.toURL(o,null);
1254                                if(str==null) return false;
1255                                return str.length()<=maxlength;
1256                        }
1257                        return Caster.toURL(o,null)!=null;
1258                    }
1259                    if(alsoAlias && type.equals("udf")) {
1260                        return isFunction(o);
1261                    }
1262                    break;
1263                case 'v':
1264                    if(type.equals("variablename")) {
1265                        return isVariableName(o);
1266                    }
1267                    else if(type.equals("void")) {
1268                        return isVoid(o);//Caster.toVoid(o,Boolean.TRUE)!=Boolean.TRUE;
1269                    }
1270                    else if(alsoAlias && type.equals("variable_name")) {
1271                        return isVariableName(o);
1272                    }
1273                    else if(alsoAlias && type.equals("variable-name")) {
1274                        return isVariableName(o);
1275                    }
1276                    if(type.equals("varchar")) {
1277                        if(maxlength>-1) {
1278                                String str = Caster.toString(o,null);
1279                                if(str==null) return false;
1280                                return str.length()<=maxlength;
1281                        }
1282                        return isCastableToString(o);
1283                    }
1284                    break;
1285                case 'x':
1286                    if(type.equals("xml")) {
1287                        return isXML(o);
1288                    }
1289                    break;
1290                case 'z':
1291                    if(alsoPattern && (type.equals("zip") || type.equals("zipcode"))) {
1292                        return Caster.toZip(o,null)!=null;
1293                    }
1294                break;
1295           }
1296        }
1297        return _isCastableTo(null,type, o);
1298    }
1299    
1300
1301    private static boolean _isCastableTo(PageContext pcMaybeNull,String type, Object o) {
1302        if(o instanceof Component) {
1303            Component comp=((Component)o);
1304            return comp.instanceOf(type);
1305        }
1306        if(o instanceof Pojo) {
1307                pcMaybeNull = ThreadLocalPageContext.get(pcMaybeNull);
1308                        return pcMaybeNull!=null && AxisCaster.toComponent(pcMaybeNull,((Pojo)o),type,null)!=null;
1309        }
1310        
1311        if(isArrayType(type) && isArray(o)){
1312                String _strType=type.substring(0,type.length()-2);
1313                short _type=CFTypes.toShort(_strType, false, (short)-1);
1314                Array arr = Caster.toArray(o,null);
1315                if(arr!=null){
1316                        Iterator<Object> it = arr.valueIterator();
1317                        while(it.hasNext()){
1318                                Object obj = it.next();
1319                                if(!isCastableTo(pcMaybeNull,_type,_strType, obj))
1320                                        return false; 
1321                        }
1322                        return true;
1323                }
1324        }
1325                return false;
1326    }
1327    
1328    
1329
1330        private static boolean isArrayType(String type) {
1331                return type.endsWith("[]");
1332        }
1333
1334        public static boolean isCastableTo(PageContext pc,short type,String strType, Object o) {
1335                switch(type){
1336                case CFTypes.TYPE_ANY:          return true;
1337                case CFTypes.TYPE_STRING:               return isCastableToString(o);
1338        case CFTypes.TYPE_BOOLEAN:      return isCastableToBoolean(o);
1339        case CFTypes.TYPE_NUMERIC:      return isCastableToNumeric(o);
1340        case CFTypes.TYPE_STRUCT:       return isCastableToStruct(o);
1341        case CFTypes.TYPE_ARRAY:        return isCastableToArray(o);
1342        case CFTypes.TYPE_QUERY:        return isQuery(o);
1343        case CFTypes.TYPE_QUERY_COLUMN: return isQueryColumn(o);
1344        case CFTypes.TYPE_DATETIME:     return isDateAdvanced(o, true);
1345        case CFTypes.TYPE_VOID:         return isVoid(o);//Caster.toVoid(o,Boolean.TRUE)!=Boolean.TRUE;
1346        case CFTypes.TYPE_BINARY:       return isCastableToBinary(o,true);
1347        case CFTypes.TYPE_TIMESPAN:     return Caster.toTimespan(o,null)!=null;
1348        case CFTypes.TYPE_UUID:         return isUUId(o);
1349        case CFTypes.TYPE_GUID:         return isGUId(o);
1350        case CFTypes.TYPE_VARIABLE_NAME:return isVariableName(o);
1351        case CFTypes.TYPE_FUNCTION:             return isFunction(o);
1352        case CFTypes.TYPE_IMAGE:        return Image.isCastableToImage(pc,o);
1353        case CFTypes.TYPE_XML:          return isXML(o);
1354                }
1355                
1356                return _isCastableTo(pc,strType, o);
1357        }
1358
1359    public synchronized static boolean isDate(String str,Locale locale, TimeZone tz,boolean lenient) {
1360        str=str.trim();
1361        tz=ThreadLocalPageContext.getTimeZone(tz);
1362        DateFormat[] df;
1363
1364        // get Calendar
1365        Calendar c=JREDateTimeUtil.getThreadCalendar(locale,tz);
1366        
1367        // datetime
1368        ParsePosition pp=new ParsePosition(0);
1369        df=FormatUtil.getDateTimeFormats(locale,tz,false);//dfc[FORMATS_DATE_TIME];
1370        Date d;
1371                for(int i=0;i<df.length;i++) {
1372                pp.setErrorIndex(-1);
1373                        pp.setIndex(0);
1374                        df[i].setTimeZone(tz);
1375                d = df[i].parse(str,pp);
1376                if (pp.getIndex() == 0 || d==null || pp.getIndex()<str.length()) continue;   
1377                        
1378                return true;
1379        }
1380        
1381                
1382            // date
1383                df=FormatUtil.getDateFormats(locale,tz,false);
1384        for(int i=0;i<df.length;i++) {
1385                pp.setErrorIndex(-1);
1386                        pp.setIndex(0);
1387                        df[i].setTimeZone(tz);
1388                        d=df[i].parse(str,pp);
1389                if (pp.getIndex() == 0 || d==null || pp.getIndex()<str.length()) continue;   
1390                return true;
1391        }
1392                
1393        // time
1394        df=FormatUtil.getTimeFormats(locale,tz,false);//dfc[FORMATS_TIME];
1395        for(int i=0;i<df.length;i++) {
1396                pp.setErrorIndex(-1);
1397                        pp.setIndex(0);
1398                        df[i].setTimeZone(tz);
1399                d=df[i].parse(str,pp);
1400                if (pp.getIndex() == 0 || d==null || pp.getIndex()<str.length()) continue;   
1401                
1402                return true;
1403        }
1404        
1405        if(lenient) return isDateSimple(str, false);
1406        return false;
1407    }
1408
1409        /**
1410         * Checks if number is valid (not infinity or NaN)
1411         * @param dbl
1412         * @return
1413         */
1414        public static boolean isValid(double dbl) {
1415                return !Double.isNaN(dbl) && !Double.isInfinite(dbl);
1416        }
1417
1418        public static boolean isAnyType(String type) {
1419                return StringUtil.isEmpty(type) || type.equalsIgnoreCase("object") || type.equalsIgnoreCase("any");
1420        }
1421}