package railo.runtime.op;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URI;
import java.sql.Blob;
import java.sql.Clob;
import java.text.DateFormat;
import java.text.ParsePosition;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Pattern;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import railo.commons.date.DateTimeUtil;
import railo.commons.date.JREDateTimeUtil;
import railo.commons.i18n.FormatUtil;
import railo.commons.lang.CFTypes;
import railo.commons.lang.StringUtil;
import railo.runtime.Component;
import railo.runtime.coder.Base64Util;
import railo.runtime.converter.WDDXConverter;
import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.ext.function.Function;
import railo.runtime.java.JavaObject;
import railo.runtime.net.mail.MailUtil;
import railo.runtime.op.date.DateCaster;
import railo.runtime.op.validators.ValidateCreditCard;
import railo.runtime.text.xml.XMLCaster;
import railo.runtime.text.xml.XMLUtil;
import railo.runtime.text.xml.struct.XMLStruct;
import railo.runtime.type.Array;
import railo.runtime.type.Closure;
import railo.runtime.type.Collection;
import railo.runtime.type.Collection.Key;
import railo.runtime.type.ObjectWrap;
import railo.runtime.type.Objects;
import railo.runtime.type.Query;
import railo.runtime.type.QueryColumn;
import railo.runtime.type.Struct;
import railo.runtime.type.UDF;
import railo.runtime.type.dt.DateTime;


/**
 * Object to test if a Object is a specific type
 */
public final class Decision {

	private static final String STRING_DEFAULT_VALUE = "this is a unique string";

	private static Pattern ssnPattern;
	private static Pattern phonePattern;
	private static Pattern zipPattern; 

	/**
	 * tests if value is a simple value (Number,String,Boolean,Date,Printable)
	 * @param value value to test
	 * @return is value a simple value
	 */
	public static boolean isSimpleValue(Object value){
		return 
				(value instanceof Number) || 
				(value instanceof String) || 
				(value instanceof Boolean) || 
				(value instanceof Date) || 
				((value instanceof Castable) && !(value instanceof Objects) && !(value instanceof Collection));
	}
	
	/**
	 * tests if value is Numeric
	 * @param value value to test
	 * @return is value numeric
	 */
	public static boolean isNumeric(Object value) {
		if(value instanceof Number) return true;
		else if(value instanceof String) {
			return isNumeric(value.toString());
		}
		
		else return false;
	}

	public static boolean isCastableToNumeric(Object o) {
		
		if(isNumeric(o)) return true;
		else if(isBoolean(o)) return true;
		else if(isDateSimple(o,false)) return true;
        else if(o == null) return true;
        else if(o instanceof ObjectWrap) return isCastableToNumeric(((ObjectWrap)o).getEmbededObject("notanumber"));

        else if(o instanceof Castable) {
        	return Decision.isValid(((Castable)o).castToDoubleValue(Double.NaN));
	        
        }
		return false;
	}
	
	public static boolean isCastableToDate(Object o) {
		if(isDateAdvanced(o, true)) return true;
		else if(isBoolean(o)) return true;
        
        else if(o instanceof ObjectWrap) return isCastableToDate(((ObjectWrap)o).getEmbededObject("notadate"));

        else if(o instanceof Castable) {
        	return ((Castable)o).castToDateTime(null)!=null;
	        
        }
		return false;
	}

	/**
	 * tests if value is Numeric
	 * @param value value to test
	 * @return is value numeric
	 */
	public static boolean isNumeric(Object value, boolean alsoBooleans) {
		if(alsoBooleans && isBoolean(value)) return true;
		return isNumeric(value);
	}
	
	/**
	 * tests if String value is Numeric
	 * @param str value to test
	 * @return is value numeric
	 */
	public static boolean isNumeric(String str) {
        if(str==null) return false; 
        str=str.trim();
        
        int pos=0; 
        int len=str.length(); 
        if(len==0) return false; 
        char curr=str.charAt(pos); 
        
        if(curr=='+' || curr=='-') { 
                if(len==++pos) return false; 
                curr=str.charAt(pos); 
        }
        
        boolean hasDot=false; 
        boolean hasExp=false; 
        for(;pos<len;pos++) { 
            curr=str.charAt(pos); 
            if(curr<'0') {
            	if(curr=='.') { 
                    if(pos+1>=len || hasDot) return false; 
                    hasDot=true; 
                } 
            	else return false;
            }
            else if(curr>'9') {
            	if(curr=='e' || curr=='E') { 
                    if(pos+1>=len || hasExp) return false; 
                    hasExp=true; 
                    hasDot=true; 
                }
            	else return false;
            }
        } 
        if(hasExp){
        	try{
        		Double.parseDouble(str);
        		return true;
        	}
        	catch( NumberFormatException e){
        		return false;
        	} 
        }
        return true; 
    } 


	public static boolean isInteger(Object value) {
		return isInteger(value,false);		
	}

	public static boolean isInteger(Object value,boolean alsoBooleans) {
		if(!alsoBooleans && value instanceof Boolean) return false;
		double dbl = Caster.toDoubleValue(value,Double.NaN);
		if(!Decision.isValid(dbl)) return false;
		int i=(int)dbl;
		return i==dbl;		
	}

	 /** tests if String value is Hex Value
	 * @param str value to test
	 * @return is value numeric
	 */
	public static boolean isHex(String str) { 
      if(str==null || str.length()==0) return false; 
      
      for(int i=str.length()-1;i>=0;i--) {
          char c=str.charAt(i);
          if(!(c>='0' && c<='9')) {
              c=Character.toLowerCase(c);
              if(!(c=='a' || c=='b' || c=='c' || c=='d' || c=='e' || c=='f'))return false;
          }
      }
      return true;
	}

	/** tests if String value is UUID Value
	 * @param obj value to test
	 * @return is value numeric
	 * @deprecated use instead <code>isUUId(Object obj)</code>
	 */
	public static boolean isUUID(Object obj) { 
		return isUUId(obj);
	}
	
	 /** tests if String value is UUID Value
	 * @param obj value to test
	 * @return is value numeric
	 */
	public static boolean isUUId(Object obj) { 
		String str=Caster.toString(obj,null);
		if(str==null) return false;

		if(str.length()==35) {	    
	        return 
	    	Decision.isHex(str.substring(0,8)) &&
	    	str.charAt(8)=='-' &&
	    	Decision.isHex(str.substring(9,13)) &&
	    	str.charAt(13)=='-' &&
	    	Decision.isHex(str.substring(14,18)) &&
	    	str.charAt(18)=='-' &&
	    	Decision.isHex(str.substring(19));
	    }
		else if(str.length()==32)
			return Decision.isHex(str);
		return false;
	}
	

	/**
	 * @param obj
	 * @return
	 * @deprecated use instead <code>isGUId(Object)</code>
	 */
	public static boolean isGUID(Object obj) { 
		return isGUId(obj);
	}

	public static boolean isGUId(Object obj) { 
		String str=Caster.toString(obj,null);
		if(str==null) return false;

        
		// GUID
	    if(str.length()==36) {	    
	        return 
	    	Decision.isHex(str.substring(0,8)) &&
	    	str.charAt(8)=='-' &&
	    	Decision.isHex(str.substring(9,13)) &&
	    	str.charAt(13)=='-' &&
	    	Decision.isHex(str.substring(14,18)) &&
	    	str.charAt(18)=='-' &&
	    	Decision.isHex(str.substring(19,23)) &&
	    	str.charAt(23)=='-' &&
	    	Decision.isHex(str.substring(24));
	    }
	    return false;
	}
	

	public static boolean isGUIdSimple(Object obj) { 
		String str=Caster.toString(obj,null);
		if(str==null) return false;

        
		// GUID
	    if(str.length()==36) {	    
	        return 
	    	str.charAt(8)=='-' &&
	    	str.charAt(13)=='-' &&
	    	str.charAt(18)=='-' &&
	    	str.charAt(23)=='-';
	    }
	    return false;
	}

	/**
	 * tests if value is a Boolean (Numbers are not acctepeted)
	 * @param value value to test
	 * @return is value boolean
	 */
	public static boolean isBoolean(Object value) {
        if(value instanceof Boolean) return true;
        else if(value instanceof String) {
			return isBoolean(value.toString());
		}
        else if(value instanceof ObjectWrap) return isBoolean(((ObjectWrap)value).getEmbededObject(null));
		else return false;
	}

	public static boolean isCastableToBoolean(Object value) {
		if(value instanceof Boolean) return true;
		if(value instanceof Number) return true;
        else if(value instanceof String) {
        	String str = (String)value;
			return isBoolean(str) || isNumeric(str);
		}
        else if(value instanceof Castable) {
            return ((Castable)value).castToBoolean(null)!=null;
            
        }
        else if(value instanceof ObjectWrap) return isCastableToBoolean(((ObjectWrap)value).getEmbededObject(null));
		else return false;
	}
	
	public static boolean isBoolean(Object value, boolean alsoNumbers) {
		if(isBoolean(value)) return true;
        else if(alsoNumbers) return isNumeric(value);
        else return false;
	}

    /**
     * tests if value is a Boolean
     * @param str value to test
     * @return is value boolean
     */
    public static boolean isBoolean(String str) {
        //str=str.trim();
        if(str.length()<2) return false;
        
        switch(str.charAt(0)) {
            case 't':
            case 'T': return str.equalsIgnoreCase("true");
            case 'f':
            case 'F': return str.equalsIgnoreCase("false");
            case 'y':
            case 'Y': return str.equalsIgnoreCase("yes");
            case 'n':
            case 'N': return str.equalsIgnoreCase("no");
        }
        return false;
    }   

	/**
	 * tests if value is DateTime Object
	 * @param value value to test
	 * @param alsoNumbers interpret also a number as date
	 * @return is value a DateTime Object
	 */
    public static boolean isDate(Object value,boolean alsoNumbers) {
    	return isDateSimple(value, alsoNumbers);
    }

	public static boolean isDateSimple(Object value,boolean alsoNumbers) {
		return isDateSimple(value, alsoNumbers, false);
	}
	public static boolean isDateSimple(Object value,boolean alsoNumbers, boolean alsoMonthString) {
		
        //return DateCaster.toDateEL(value)!=null;
		if(value instanceof DateTime) 		return true;
		else if(value instanceof Date) 		return true;
		// wrong timezone but this isent importend because date will not be importend
		else if(value instanceof String) 	return DateCaster.toDateSimple(value.toString(),alsoNumbers,alsoMonthString,TimeZone.getDefault(),null)!=null;
		else if(value instanceof ObjectWrap) {
        	return isDateSimple(((ObjectWrap)value).getEmbededObject(null),alsoNumbers);
        }
        else if(value instanceof Castable) 	{
		    return ((Castable)value).castToDateTime(null)!=null;
            
		}
		else if(alsoNumbers && value instanceof Number) return true;
		else if(value instanceof Calendar) return true;
		return false;
	}
	
	public static boolean isDateAdvanced(Object value,boolean alsoNumbers) {
	    //return DateCaster.toDateEL(value)!=null;
		if(value instanceof DateTime) 		return true;
		else if(value instanceof Date) 		return true;
		// wrong timezone but this isent importend because date will not be importend
		else if(value instanceof String) 	return DateCaster.toDateAdvanced(value.toString(),alsoNumbers,TimeZone.getDefault(),null)!=null;
		else if(value instanceof Castable) 	{
		    return ((Castable)value).castToDateTime(null)!=null;
             
		}
		else if(alsoNumbers && value instanceof Number) return true;
		else if(value instanceof ObjectWrap) {
        	return isDateAdvanced(((ObjectWrap)value).getEmbededObject(null),alsoNumbers);
        }
        else if(value instanceof Calendar) return true;
		return false;
	}
	
	private static char[] DATE_DEL=new char[]{'.','/','-'};
	
	public static boolean isUSDate(Object value) {
		String str = Caster.toString(value,"");
		return isUSorEuroDateEuro(str,false);
	}
	
	public static boolean isUSDate(String str) {
		return isUSorEuroDateEuro(str,false);
	}
	
	public static boolean isEuroDate(Object value) {
		String str = Caster.toString(value,"");
		return isUSorEuroDateEuro(str,true);
	}
	
	public static boolean isEuroDate(String str) {
		return isUSorEuroDateEuro(str,true);
	}
	
	private static boolean isUSorEuroDateEuro(String str, boolean isEuro) {
		if(StringUtil.isEmpty(str)) return false;
		
		for(int i=0;i<DATE_DEL.length;i++) {
			Array arr = railo.runtime.type.util.ListUtil.listToArrayRemoveEmpty(str,DATE_DEL[i]);
			if(arr.size()!=3) continue;

			int month=Caster.toIntValue(	arr.get(isEuro?2:1,Constants.INTEGER_0),Integer.MIN_VALUE);
			int day=Caster.toIntValue(		arr.get(isEuro?1:2,Constants.INTEGER_0),Integer.MIN_VALUE);
			int year=Caster.toIntValue(		arr.get(3,Constants.INTEGER_0),Integer.MIN_VALUE);

			
			if(month==Integer.MIN_VALUE) continue;
			if(month>12) continue;
			if(day==Integer.MIN_VALUE) continue;
			if(day>31) continue;
			if(year==Integer.MIN_VALUE) continue;
			if(DateTimeUtil.getInstance().toTime(null,year, month, day, 0, 0, 0,0, Long.MIN_VALUE)==Long.MIN_VALUE) continue;
			return true;
		}
		return false;
	}
	
	public static boolean isCastableToStruct(Object o) {
		if(isStruct(o)) return true;
		if(o == null) return false;
		else if(o instanceof ObjectWrap) {
        	if(o instanceof JavaObject ) return true;
            return isCastableToStruct(((ObjectWrap)o).getEmbededObject(null));
        }
		if(Decision.isSimpleValue(o)){
			return false;
		}
		//if(isArray(o) || isQuery(o)) return false;
        return false;
	}

	/**
	 * tests if object is a struct 
	 * @param o
	 * @return is struct or not
	 */
	public static boolean isStruct(Object o) {
		if(o instanceof Struct) return true;
        else if(o instanceof Map)return true;
        else if(o instanceof Node)return true;
		return false;
	}

	
	/**
	 * can this type be casted to a array
	 * @param o
	 * @return
	 * @throws PageException
	 */
	public static boolean isCastableToArray(Object o) {
        if(isArray(o)) return true;
        //else if(o instanceof XMLStruct) return true;
        else if(o instanceof Struct) {
            Struct sct=(Struct) o;
            Iterator<Key> it = sct.keyIterator();
            try {
                while(it.hasNext()) {
                	Caster.toIntValue(it.next().getString());
                }
                return true;
            } 
            catch (Throwable t) {
                return false;
            }
        }
        return false;
    }
	
	/**
	 * tests if object is a array 
	 * @param o
	 * @return is array or not
	 */
	public static boolean isArray(Object o) {
		if(o instanceof Array) 				return true;
		if(o instanceof List) 				return true;
		if(isNativeArray(o))				return true;
		if(o instanceof ObjectWrap) {
            return isArray(((ObjectWrap)o).getEmbededObject(null));
        }
        return false;
	}

	/**
	 * tests if object is a native java array 
	 * @param o
	 * @return is a native (java) array
	 */
	public static boolean isNativeArray(Object o) {
		//return o.getClass().isArray();
		if(o instanceof Object[]) 			return true;
		else if(o instanceof boolean[])		return true;
		else if(o instanceof byte[])		return true;
		else if(o instanceof char[])		return true;
		else if(o instanceof short[])		return true;
		else if(o instanceof int[])			return true;
		else if(o instanceof long[])		return true;
		else if(o instanceof float[])		return true;
		else if(o instanceof double[])		return true;
		return false;
	}

	/**
	 * tests if object is catable to a binary  
	 * @param object
	 * @return boolean
	 */
	public static boolean isCastableToBinary(Object object,boolean checkBase64String) {
		if(isBinary(object))return true;
		if(object instanceof InputStream) return true;
		if(object instanceof ByteArrayOutputStream) return true;
		if(object instanceof Blob) return true;
        
		// Base64 String
		if(!checkBase64String) return false;
		String str = Caster.toString(object,null);
        if(str==null) return false;
        return Base64Util.isBase64(str);
		
	}

	/**
	 * tests if object is a binary  
	 * @param object
	 * @return boolean
	 */
	public static boolean isBinary(Object object) {
		if(object instanceof byte[]) return true;
		if(object instanceof ObjectWrap) return isBinary(((ObjectWrap)object).getEmbededObject(""));
		return false;
	}

	/**
	 * tests if object is a Component  
	 * @param object
	 * @return boolean
	 */
	public static boolean isComponent(Object object) {
		return object instanceof Component;
	}

	/**
	 * tests if object is a Query  
	 * @param object
	 * @return boolean
	 */
	public static boolean isQuery(Object object) {
		if(object instanceof Query)return true;
		else if(object instanceof ObjectWrap) {
            return isQuery(((ObjectWrap)object).getEmbededObject(null));
        }
        return false;
	}
	public static boolean isQueryColumn(Object object) {
		if(object instanceof QueryColumn)return true;
		else if(object instanceof ObjectWrap) {
            return isQueryColumn(((ObjectWrap)object).getEmbededObject(null));
        }
        return false;
	}

	/**
	 * tests if object is a binary  
	 * @param object
	 * @return boolean
	 */
	public static boolean isUserDefinedFunction(Object object) {
		return object instanceof UDF;
	}
	
	/**
	 * tests if year is a leap year 
	 * @param year year to check
	 * @return boolean
	 */
	public static final boolean isLeapYear(int year) {
		return DateTimeUtil.getInstance().isLeapYear(year);
		//return new GregorianCalendar().isLeapYear(year);
    }
	
	/**
	 * tests if object is a WDDX Object 
	 * @param o Object to check
	 * @return boolean
	 */
	public static boolean isWddx(Object o) {
		if(!(o instanceof String)) return false;
		String str=o.toString();
		if(!(str.indexOf("wddxPacket")>0)) return false;
		
		// wrong timezone but this isent importend because date will not be used
		WDDXConverter converter =new WDDXConverter(TimeZone.getDefault(),false,true);
		try {
			converter.deserialize(Caster.toString(o),true);
		} 
		catch (Exception e) {
			return false;
		}
		return true;
	}
	
	/**
	 * tests if object is a XML Object 
	 * @param o Object to check
	 * @return boolean
	 */
	public static boolean isXML(Object o) {
		if(o instanceof Node || o instanceof NodeList) return true;
		if(o instanceof ObjectWrap) {
            return isXML(((ObjectWrap)o).getEmbededObject(null));
        }
        try {
            XMLCaster.toXMLStruct(XMLUtil.parse(XMLUtil.toInputSource(null, o),null,false),false);
            return true;
        }
        catch(Exception outer) {
            return false;
        }
		
	}
	
	public static boolean isVoid(Object o) {
		if(o==null)return true;
        else if(o instanceof String)	return o.toString().length()==0;
        else if(o instanceof Number)	return ((Number)o).intValue()==0;
        else if(o instanceof Boolean)	return ((Boolean)o).booleanValue()==false ;
        else if(o instanceof ObjectWrap)return isVoid(((ObjectWrap)o).getEmbededObject(("isnotnull")));
		return false;
	}
	
	
	/**
	 * tests if object is a XML Element Object 
	 * @param o Object to check
	 * @return boolean
	 */
	public static boolean isXMLElement(Object o) {
		return o instanceof Element;
	}
	
	/**
	 * tests if object is a XML Document Object 
	 * @param o Object to check
	 * @return boolean
	 */
	public static boolean isXMLDocument(Object o) {
		return o instanceof Document;
	}
	
	/**
	 * tests if object is a XML Root Element Object 
	 * @param o Object to check
	 * @return boolean
	 */
	public static boolean isXMLRootElement(Object o) {
		if(o instanceof Node) {
			Node n=(Node)o;
			if(n instanceof XMLStruct)n=((XMLStruct)n).toNode();
			return n.getOwnerDocument()!=null && n.getOwnerDocument().getDocumentElement()==n;
		}
		return false;
	}

	/**
	 * @param obj
	 * @return returns if string represent a variable name
	 */
	public static boolean isVariableName(Object obj) {
		if(obj instanceof String) return isVariableName((String)obj);
		return false;
	}

	public static boolean isFunction(Object obj) {
		if(obj instanceof UDF)return true;
		else if(obj instanceof ObjectWrap) {
            return isFunction(((ObjectWrap)obj).getEmbededObject(null));
        }
        return false;
	}

	public static boolean isClosure(Object obj) {
		if(obj instanceof Closure)return true;
		else if(obj instanceof ObjectWrap) {
            return isClosure(((ObjectWrap)obj).getEmbededObject(null));
        }
        return false;
	}

	/**
	 * @param string
	 * @return returns if string represent a variable name
	 */
	public static boolean isVariableName(String string) {
		if(string.length()==0)return false;
		int len=string.length();
		int pos=0;
		while(pos<len) {
		    char first=string.charAt(pos);
		    if(!((first>='a' && first<='z')||(first>='A' && first<='Z')||(first=='_')))
				return false;
		    pos++;
		    for(;pos<len;pos++) {
				char c=string.charAt(pos);
				if(!((c>='a' && c<='z')||(c>='A' && c<='Z')||(c>='0' && c<='9')||(c=='_')))
					break;
			}
		    if(pos==len) return true;
		    if(string.charAt(pos)=='.')pos++;
		}
		return false;
	}
	
	/**
	 * @param string
	 * @return returns if string represent a variable name
	 */
	public static boolean isSimpleVariableName(String string) {
		if(string.length()==0)return false;
		
		char first=string.charAt(0);
		if(!((first>='a' && first<='z')||(first>='A' && first<='Z')||(first=='_')))
			return false;
		for(int i=string.length()-1;i>0;i--) {
			char c=string.charAt(i);
			if(!((c>='a' && c<='z')||(c>='A' && c<='Z')||(c>='0' && c<='9')||(c=='_')))
				return false;
		}	
		return true;
	}
	
	/**
	 * @param key
	 * @return returns if string represent a variable name
	 */
	public static boolean isSimpleVariableName(Collection.Key key) {
		String strKey = key.getLowerString();
		if(strKey.length()==0)return false;
		
		char first=strKey.charAt(0);
		if(!((first>='a' && first<='z')||(first=='_')))
			return false;
		for(int i=strKey.length()-1;i>0;i--) {
			char c=strKey.charAt(i);
			if(!((c>='a' && c<='z')||(c>='0' && c<='9')||(c=='_')))
				return false;
		}	
		return true;
	}

	/**
	 * returns if object is a CFML object
	 * @param o Object to check
	 * @return is or not
	 */
	public static boolean isObject(Object o) {
		return isComponent(o)
		
			|| (!isArray(o)
			&& !isQuery(o)
			&& !isSimpleValue(o)
			&& !isStruct(o)
			&& !isUserDefinedFunction(o)
			&& !isXML(o));
	}

    /**
     * @param obj
     * @return return if a String is "Empty", that means NULL or String with length 0 (whitespaces will not counted) 
     */
    public static boolean isEmpty(Object obj) {
        if(obj instanceof String)return StringUtil.isEmpty((String)obj);
        return obj==null;
    }


    /**
     * @deprecated use instead <code>StringUtil.isEmpty(String)</code>
     * @param str
     * @return return if a String is "Empty", that means NULL or String with length 0 (whitespaces will not counted) 
     */
    public static boolean isEmpty(String str) {
        return StringUtil.isEmpty(str);
    }
    
    /**
     * @deprecated use instead <code>StringUtil.isEmpty(String)</code>
     * @param str
     * @param trim 
     * @return return if a String is "Empty", that means NULL or String with length 0 (whitespaces will not counted) 
     */
    public static boolean isEmpty(String str, boolean trim) {
        return StringUtil.isEmpty(str,trim);
    }
    

	/**
	 * returns if a value is a credit card
	 * @param value
	 * @return is credit card
	 */
	public static boolean isCreditCard(Object value) {
		return ValidateCreditCard.isValid(Caster.toString(value,"0"));
	}
	

	/**
	 * returns if given object is a email
	 * @param value
	 * @return
	 */
	public static boolean isEmail(Object value) {

        return MailUtil.isValidEmail(value);
	}	
	
	
	
	/**
	 * returns if given object is a social security number (usa)
	 * @param value
	 * @return
	 */
	public static boolean isSSN(Object value) {
		String str = Caster.toString(value,null);
		if(str==null)return false;
		
		if(ssnPattern==null)
			ssnPattern=Pattern.compile("^[0-9]{3}[-|]{1}[0-9]{2}[-|]{1}[0-9]{4}$");
		
		return ssnPattern.matcher(str.trim()).matches();
		
	}
	
	/**
	 * returns if given object is a phone
	 * @param value
	 * @return
	 */
	public static boolean isPhone(Object value) {
		String str = Caster.toString(value,null);
		if(str==null)return false;
		
		if(phonePattern==null)
			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})?$");
		return phonePattern.matcher(str.trim()).matches();
		
	}	

	/**
	 * returns true if the given object is a valid URL
	 * @param value
	 * @return
	 */
    public static boolean isURL( Object value ) {

        String str = Caster.toString( value, null );

        if ( str == null )                      return false;
        if ( str.indexOf( ':' ) == -1 )         return false;

        str = str.toLowerCase().trim();

        if ( !str.startsWith( "http://" )
          && !str.startsWith( "https://" )
          && !str.startsWith( "file://" )
          && !str.startsWith( "ftp://" )
          && !str.startsWith( "mailto:" )
          && !str.startsWith( "news:" )
          && !str.startsWith( "urn:" ) )        return false;

        try {

            URI uri = new URI( str );
            String proto = uri.getScheme();

            if ( proto == null )                return false;

            if ( proto.equals( "http" ) || proto.equals( "https" ) || proto.equals( "file" ) || proto.equals( "ftp" ) ) {

                if ( uri.getHost() == null )    return false;

                String path = uri.getPath();
                if ( path != null ) {

                    int len = path.length();
                    for ( int i=0; i<len; i++ ) {

                        if ( "?<>:*|\"".indexOf( path.charAt( i ) ) > -1 )
                            return false;
                    }
                }
            }

            return true;
        }
        catch ( Exception ex ) {

            return false;
        }
    }
	
	/**
	 * returns if given object is a zip code
	 * @param value
	 * @return
	 */
	public static boolean isZipCode(Object value) {
		String str = Caster.toString(value,null);
		if(str==null)return false;
		
		if(zipPattern==null)
			zipPattern=Pattern.compile("([0-9]{5,5})|([0-9]{5,5}[- ]{1}[0-9]{4,4})");
		return zipPattern.matcher(str.trim()).matches();
	}

	public static boolean isString(Object o) {
		if(o instanceof String) 		return true;
        else if(o instanceof Boolean)	return true;
        else if(o instanceof Number) 	return true;
        else if(o instanceof Date) 		return true;
        else if(o instanceof Castable) {
            return ((Castable)o).castToString(STRING_DEFAULT_VALUE)!=STRING_DEFAULT_VALUE;
            
        }
        else if(o instanceof Clob) 		return true;
        else if(o instanceof Node) 		return true;
        else if(o instanceof Map || o instanceof List || o instanceof Function) return false;
        else if(o == null) return true;
        else if(o instanceof ObjectWrap) return isString(((ObjectWrap)o).getEmbededObject(""));
		return true;
	}
	public static boolean isCastableToString(Object o) {
		return isString(o);
	}
	
    
    public static boolean isValid(String type, Object value) throws ExpressionException {
    	type=StringUtil.toLowerCase(type.trim());
    	char first = type.charAt(0);
    	switch(first) {
    	case 'a':
    		if("any".equals(type))			return true;//isSimpleValue(value);
    		if("array".equals(type))		return isArray(value);
    	break;
    	case 'b':
        	if("binary".equals(type))		return isBinary(value);
    		if("boolean".equals(type))		return isBoolean(value,true);
        break;
    	case 'c':
        	if("creditcard".equals(type))	return isCreditCard(value);
        	if("component".equals(type))	return isComponent(value);
        	if("cfc".equals(type))			return isComponent(value);
        break;
    	case 'd':
    		if("date".equals(type))			return isDateAdvanced(value,true);  // ist zwar nicht logisch aber ident. zu Neo
    		if("double".equals(type))		return isCastableToNumeric(value);
        break;
    	case 'e':
    		if("eurodate".equals(type))		return isEuroDate(value); 
    		if("email".equals(type))		return isEmail(value);
        break;
        case 'f':
        	if("float".equals(type))		return isNumeric(value,true);
        	if("function".equals(type))		return isFunction(value);
        break;
        case 'g':
        	if("guid".equals(type))			return isGUId(value);
    	break;
        case 'i':
        	if("integer".equals(type))		return isInteger(value,false);
        break;
        case 'n':
        	if("numeric".equals(type))		return isCastableToNumeric(value);
        	if("number".equals(type))		return isCastableToNumeric(value);
        	if("node".equals(type))			return isXML(value); 
        break;
        case 'p':
        	if("phone".equals(type))		return isPhone(value);
        break;
        case 'q':
        	if("query".equals(type))		return isQuery(value);
    	break;
        case 's':
        	if("simple".equals(type))		return isSimpleValue(value);	
    		if("struct".equals(type))		return isStruct(value);
    		if("ssn".equals(type))			return isSSN(value);
    		if("social_security_number".equals(type))return isSSN(value);
    		if("string".equals(type))		return isString(value);
        break;
        case 't':
        	if("telephone".equals(type))	return isPhone(value);
    		if("time".equals(type))			return isDateAdvanced(value,false);
        break;
        case 'u':
        	if("usdate".equals(type))		return isUSDate(value); 
    		if("uuid".equals(type))			return isUUId(value);
    		if("url".equals(type))			return isURL(value);
        break;
        case 'v':
        	if("variablename".equals(type))	return isVariableName(Caster.toString(value,""));
        break;
        case 'x':
        	if("xml".equals(type))	return isXML(value); // DIFF 23
        break;
        case 'z':
        	if("zip".equals(type))			return isZipCode(value);
    		if("zipcode".equals(type))		return isZipCode(value);
    	break;
        }
    	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]");
		
    }
    
    /**
     * checks if a value is castable to a certain type
     * @param type any,array,boolean,binary, ...
     * @param o value to check
     * @param alsoPattern also check patterns like creditcards,email,phone ...
     * @param maxlength only used for email,url, string, ignored otherwise
     * @return
     */
    public static boolean isCastableTo(String type, Object o, boolean alsoAlias, boolean alsoPattern, int maxlength) {
        
    	type=StringUtil.toLowerCase(type).trim();
        if(type.length()>2) {
            char first=type.charAt(0);
            switch(first) {
                case 'a':
                    if(type.equals("any")) {
                        return true;
                    }
                    else if(type.equals("array")) {
                        return isCastableToArray(o);
                    }
                    break;
                case 'b':
                    if(type.equals("boolean") || (alsoAlias && type.equals("bool"))) {
                        return isCastableToBoolean(o);
                    }
                    else if(type.equals("binary")) {
                        return isCastableToBinary(o,true);
                    }
                    else if(alsoAlias && type.equals("bigint")) {
                    	return isCastableToNumeric(o);
                    }
                    else if(type.equals("base64")) {
                        return Caster.toBase64(o,null,null)!=null;
                    }
                    break;
                case 'c':
                	if(alsoPattern && type.equals("creditcard")) {
                    	return Caster.toCreditCard(o,null)!=null;
                    }
                	if(alsoPattern && type.equals("char")) {
                		if(maxlength>-1) {
                    		String str = Caster.toString(o,null);
                    		if(str==null) return false;
                    		return str.length()<=maxlength;
                    	}
                        return isCastableToString(o);
                    }
                    break;
                case 'd':
                	if(type.equals("date")) {
                        return isDateAdvanced(o, true);
                    }
                    else if(type.equals("datetime")) {
                        return isDateAdvanced(o, true);
                    }
                    else if(alsoAlias && type.equals("double")) {
                        return isCastableToNumeric(o);
                    }
                    else if(alsoAlias && type.equals("decimal")) {
                        return Caster.toDecimal(o,null)!=null;
                    }
                    break;
                case 'e':
                    if(alsoAlias && type.equals("eurodate")) {
                    	return isDateAdvanced(o, true);
                    }
                    else if(alsoPattern && type.equals("email")) {
                    	if(maxlength>-1) {
                    		String str = Caster.toEmail(o,null);
                    		if(str==null) return false;
                    		return str.length()<=maxlength;
                    	}
                        return Caster.toEmail(o,null)!= null;
                    }
                    break;
                case 'f':
                    if(alsoAlias && type.equals("float")) {
                    	return isCastableToNumeric(o);
                    }
                    if(type.equals("function")) {
                    	return isFunction(o);
                    }
                    break;
                case 'g':
                    if(type.equals("guid")) {
                        return isGUId(o);
                    }
                    break;
                case 'i':
                    if(alsoAlias && (type.equals("integer") || type.equals("int"))) {
                        return isCastableToNumeric(o);
                    }
                    break;
                case 'l':
                    if(alsoAlias && type.equals("long")) {
                        return isCastableToNumeric(o);
                    }
                    break;
                case 'n':
                    if(type.equals("numeric")) {
                        return isCastableToNumeric(o);
                    }
                    else if(type.equals("number")) {
                        return isCastableToNumeric(o);
                    }
                    
                    if(alsoAlias) {
		        	    if(type.equals("node")) return isXML(o);
		        	    else if(type.equals("nvarchar") || type.equals("nchar")) {
		        	    	if(maxlength>-1) {
	                    		String str = Caster.toString(o,null);
	                    		if(str==null) return false;
	                    		return str.length()<=maxlength;
	                    	}
	                        return isCastableToString(o);
		        	    }
	        	    }
                    
                    break;
                case 'o':
                    if(type.equals("object")) {
                        return true;
                    }
                    else if(alsoAlias && type.equals("other")) {
                        return true;
                    }
                    break;
                case 'p':
                    if(alsoPattern && type.equals("phone")) {
                        return Caster.toPhone(o,null)!=null;
                    }
                    break;
                case 'q':
                    if(type.equals("query")) {
                        return isQuery(o);
                    }
                    
	        	    if(type.equals("querycolumn")) return isQueryColumn(o);
	        	    
                    break;
                case 's':
                    if(type.equals("string")) {
                    	if(maxlength>-1) {
                    		String str = Caster.toString(o,null);
                    		if(str==null) return false;
                    		return str.length()<=maxlength;
                    	}
                        return isCastableToString(o);
                    }
                    else if(type.equals("struct")) {
                        return isCastableToStruct(o);
                    }
                    else if(alsoAlias && type.equals("short")) {
                        return isCastableToNumeric(o);
                    }
                    else if(alsoPattern && (type.equals("ssn") ||type.equals("social_security_number"))) {
                        return Caster.toSSN(o,null)!=null;
                    }
                    break;
                case 't':
                    if(type.equals("timespan")) {
                        return Caster.toTimespan(o,null)!=null;
                    }
                    if(type.equals("time")) {
                    	return isDateAdvanced(o, true);
                    }
                    if(alsoPattern && type.equals("telephone")) {
                    	return Caster.toPhone(o,null)!=null;
                    }
                    

	        	    if(alsoAlias && type.equals("timestamp")) return isDateAdvanced(o, true);
	        	    if(alsoAlias && type.equals("text")) {
	        	    	if(maxlength>-1) {
                    		String str = Caster.toString(o,null);
                    		if(str==null) return false;
                    		return str.length()<=maxlength;
                    	}
                        return isCastableToString(o);
	        	    }
	        	    
                case 'u':
                    if(type.equals("uuid")) {
                        return isUUId(o);
                    }
                    if(alsoAlias && type.equals("usdate")) {
                    	return isDateAdvanced(o, true);
                    }
                    if(alsoPattern && type.equals("url")) {
                    	if(maxlength>-1) {
                    		String str = Caster.toURL(o,null);
                    		if(str==null) return false;
                    		return str.length()<=maxlength;
                    	}
                        return Caster.toURL(o,null)!=null;
                    }
                    if(alsoAlias && type.equals("udf")) {
                    	return isFunction(o);
                    }
                    break;
                case 'v':
                    if(type.equals("variablename")) {
                        return isVariableName(o);
                    }
                    else if(type.equals("void")) {
                        return isVoid(o);//Caster.toVoid(o,Boolean.TRUE)!=Boolean.TRUE;
                    }
                    else if(alsoAlias && type.equals("variable_name")) {
                        return isVariableName(o);
                    }
                    else if(alsoAlias && type.equals("variable-name")) {
                        return isVariableName(o);
                    }
                    if(type.equals("varchar")) {
                    	if(maxlength>-1) {
                    		String str = Caster.toString(o,null);
                    		if(str==null) return false;
                    		return str.length()<=maxlength;
                    	}
                        return isCastableToString(o);
                    }
                    break;
                case 'x':
                    if(type.equals("xml")) {
                        return isXML(o);
                    }
                    break;
                case 'z':
                    if(alsoPattern && (type.equals("zip") || type.equals("zipcode"))) {
                        return Caster.toZip(o,null)!=null;
                    }
                break;
           }
        }
        if(o instanceof Component) {
            Component comp=((Component)o);
            return comp.instanceOf(type);
        }
        if(isArrayType(type) && isArray(o)){
        	String t=type.substring(0,type.length()-2);
        	Array arr = Caster.toArray(o,null);
        	if(arr!=null){
        		Iterator<Object> it = arr.valueIterator();
        		while(it.hasNext()){
        			if(!isCastableTo(t, it.next(), alsoAlias,alsoPattern,-1))
        				return false;
        			
        		}
        		return true;
        	}
        	
        }
		return false;
    }
    
    

	private static boolean isArrayType(String type) {
		return type.endsWith("[]");
	}

	public static boolean isCastableTo(short type,String strType, Object o) {
		switch(type){
		case CFTypes.TYPE_ANY:          return true;
		case CFTypes.TYPE_STRING:		return isCastableToString(o);
        case CFTypes.TYPE_BOOLEAN:      return isCastableToBoolean(o);
        case CFTypes.TYPE_NUMERIC:      return isCastableToNumeric(o);
        case CFTypes.TYPE_STRUCT:       return isCastableToStruct(o);
        case CFTypes.TYPE_ARRAY:        return isCastableToArray(o);
        case CFTypes.TYPE_QUERY:        return isQuery(o);
        case CFTypes.TYPE_QUERY_COLUMN: return isQueryColumn(o);
        case CFTypes.TYPE_DATETIME:     return isDateAdvanced(o, true);
        case CFTypes.TYPE_VOID:         return isVoid(o);//Caster.toVoid(o,Boolean.TRUE)!=Boolean.TRUE;
        case CFTypes.TYPE_BINARY:       return isCastableToBinary(o,true);
        case CFTypes.TYPE_TIMESPAN:     return Caster.toTimespan(o,null)!=null;
        case CFTypes.TYPE_UUID:         return isUUId(o);
        case CFTypes.TYPE_GUID:         return isGUId(o);
        case CFTypes.TYPE_VARIABLE_NAME:return isVariableName(o);
        case CFTypes.TYPE_FUNCTION:		return isFunction(o);
        case CFTypes.TYPE_XML:          return isXML(o);
		}
		
        if(o instanceof Component) {
        	Component comp=((Component)o);
            return comp.instanceOf(strType);
        }
        if(isArrayType(strType) && isArray(o)){
        	String _strType=strType.substring(0,strType.length()-2);
        	short _type=CFTypes.toShort(_strType, false, (short)-1);
        	Array arr = Caster.toArray(o,null);
        	if(arr!=null){
        		Iterator<Object> it = arr.valueIterator();
        		while(it.hasNext()){
        			Object obj = it.next();
        			if(!isCastableTo(_type,_strType, obj))
        				return false;
        		}
        		return true;
        	}
        	
        }
        
		return false;
	}

    public synchronized static boolean isDate(String str,Locale locale, TimeZone tz,boolean lenient) {
    	str=str.trim();
    	tz=ThreadLocalPageContext.getTimeZone(tz);
    	DateFormat[] df;

    	// get Calendar
        Calendar c=JREDateTimeUtil.getThreadCalendar(locale);
        
        // datetime
        ParsePosition pp=new ParsePosition(0);
        df=FormatUtil.getDateTimeFormats(locale,tz,false);//dfc[FORMATS_DATE_TIME];
        Date d;
		for(int i=0;i<df.length;i++) {
    		pp.setErrorIndex(-1);
			pp.setIndex(0);
			df[i].setTimeZone(tz);
        	d = df[i].parse(str,pp);
        	if (pp.getIndex() == 0 || d==null || pp.getIndex()<str.length()) continue;	
			
        	return true;
        }
        
	    	
	    // date
		df=FormatUtil.getDateFormats(locale,tz,false);
        for(int i=0;i<df.length;i++) {
    		pp.setErrorIndex(-1);
			pp.setIndex(0);
			df[i].setTimeZone(tz);
			d=df[i].parse(str,pp);
        	if (pp.getIndex() == 0 || d==null || pp.getIndex()<str.length()) continue;	
        	return true;
        }
		
        // time
        df=FormatUtil.getTimeFormats(locale,tz,false);//dfc[FORMATS_TIME];
        for(int i=0;i<df.length;i++) {
        	pp.setErrorIndex(-1);
			pp.setIndex(0);
			df[i].setTimeZone(tz);
        	d=df[i].parse(str,pp);
        	if (pp.getIndex() == 0 || d==null || pp.getIndex()<str.length()) continue;	
        	
        	return true;
        }
        
        if(lenient) return isDateSimple(str, false);
        return false;
    }

	/**
	 * Checks if number is valid (not infinity or NaN)
	 * @param dbl
	 * @return
	 */
	public static boolean isValid(double dbl) {
		return !Double.isNaN(dbl) && !Double.isInfinite(dbl);
	}
}